How to make rounded rectangle in OpenGL, or any polygon with rounded corners?
I came across this fixing a crash in some open-source software - the non-GL version worked fine but basically the intention was to implement a rounded rectangle but the developer was too lazy for that and decided to force a crash instead :-(
Although I think @vime's answer is succinct and complete, I have seen lots of similar examples, none of which gave me any confidence and that I thought were non-obvious, so here's mine for the record... the calling function implements the 4-corners ( code snippet )...
glBegin(GL_POLYGON);
// top-left corner
DrawGLRoundedCorner(x, y + radius, 3 * PI / 2, PI / 2, radius);
// top-right
DrawGLRoundedCorner(x + size_x - radius, y, 0.0, PI / 2, radius);
// bottom-right
DrawGLRoundedCorner(x + size_x, y + size_y - radius, PI / 2, PI / 2, radius);
// bottom-left
DrawGLRoundedCorner(x + radius, y + size_y, PI, PI / 2, radius);
glEnd();
... and the arc-section function DrawGLRoundedCorner(). Note that this assumes that glBegin() has already been called and plots both the start and the end of the arc - which is why you don't need to explicitly add the vertices at the end of the sides.
void DrawGLRoundedCorner(int x, int y, double sa, double arc, float r) {
// centre of the arc, for clockwise sense
float cent_x = x + r * cos(sa + PI / 2);
float cent_y = y + r * sin(sa + PI / 2);
// build up piecemeal including end of the arc
int n = ceil(N_ROUNDING_PIECES * arc / PI * 2);
for (int i = 0; i <= n; i++) {
double ang = sa + arc * (double)i / (double)n;
// compute the next point
float next_x = cent_x + r * sin(ang);
float next_y = cent_y - r * cos(ang);
glVertex2f(next_x, next_y);
}
}
By using a different glBegin such as with GL_LINE_LOOP I think you would end up with a non-filled rounded rectangle. For larger corner radii there might be a need to use various anti-aliasing hints or the like to make it look prettier, but there are other posts regarding that.
Hope that helps someone.
If using polygons is absolutely required, for example if objects with rounding need to be scaled or zoomed a lot or if amount of rounding needs to be controlled, it is possible to break rectangle into several sub-objects.
There are at least three rectangular parts and four corners. Calculating corner coordinates is easy. Just find a point from circle and build triangles like in picture above.
float anglerad = PI * angle / 180.0f;
float x = sinf(anglerad) * radius;
float y = cosf(anglerad) * radius;
It will still have sharp edges, but more points make corners more round. Small objects need less points than big objects.
Easy route is to use GL_TRIANGLE_FAN for corners. However with OpenGL ES it might be wise to minimize amount of OpenGL calls and just use more vertices as it is possible to build whole object as GL_TRIANGLE_STRIP.
This approached can be used with any shape. With a rectangle, angle of corner is always 90 degrees, but with other shapes angle needs to be calculated from the edges.
Another approach is called 9-slice scaling. Rectangle and texture are broken into 9 slices. Actual rounding is in corner of a texture. Idea is that corners are not scaled, but maintain their original size. This approach is widely used pattern in UI-design allowing variable size UI-elements like buttons. Its advantage is that one rectangle needs only these 9 quads to render. But it will look bad if also corners need to be scaled and especially if texture is low resolution.
The following code is coping from my own project, I have added some comments to explain in the code. If will draw a gradient rounded rectangle without border.
#define GLW_SMALL_ROUNDED_CORNER_SLICES 5 // How many vertexes you want of each corner
#define glwR(rgb) ((float)(((rgb) >> 16) & 0xff) / 255)
#define glwG(rgb) ((float)(((rgb) >> 8) & 0xff) / 255)
#define glwB(rgb) ((float)(((rgb)) & 0xff) / 255)
typedef struct glwVec2 {
float x;
float y;
} glwVec2;
static glwVec2 glwRoundedCorners[GLW_SMALL_ROUNDED_CORNER_SLICES] = {{0}}; // This array keep the generated vertexes of one corner
static void createRoundedCorners(glwVec2 *arr, int num) {
// Generate the corner vertexes
float slice = M_PI / 2 / num;
int i;
float a = 0;
for (i = 0; i < num; a += slice, ++i) {
arr[i].x = cosf(a);
arr[i].y = sinf(a);
}
}
createRoundedCorners(glwRoundedCorners, GLW_SMALL_ROUNDED_CORNER_SLICES);
void glwDrawRoundedRectGradientFill(float x, float y, float width, float height,
float radius, unsigned int topColor, unsigned int bottomColor) {
float left = x;
float top = y;
float bottom = y + height - 1;
float right = x + width - 1;
int i;
glDisable(GL_TEXTURE_2D);
glBegin(GL_QUAD_STRIP);
// Draw left rounded side.
for (i = 0; i < GLW_SMALL_ROUNDED_CORNER_SLICES; ++i) {
glColor3f(glwR(bottomColor), glwG(bottomColor), glwB(bottomColor));
glVertex2f(left + radius - radius * glwRoundedCorners[i].x,
bottom - radius + radius * glwRoundedCorners[i].y);
glColor3f(glwR(topColor), glwG(topColor), glwB(topColor));
glVertex2f(left + radius - radius * glwRoundedCorners[i].x,
top + radius - radius * glwRoundedCorners[i].y);
}
// Draw right rounded side.
for (i = GLW_SMALL_ROUNDED_CORNER_SLICES - 1; i >= 0; --i) {
glColor3f(glwR(bottomColor), glwG(bottomColor), glwB(bottomColor));
glVertex2f(right - radius + radius * glwRoundedCorners[i].x,
bottom - radius + radius * glwRoundedCorners[i].y);
glColor3f(glwR(topColor), glwG(topColor), glwB(topColor));
glVertex2f(right - radius + radius * glwRoundedCorners[i].x,
top + radius - radius * glwRoundedCorners[i].y);
}
glEnd();
}
If you want draw the border, here is the code.
static void glwDrawRightTopVertexs(float left, float top, float right,
float bottom, float radius) {
int i;
for (i = GLW_SMALL_ROUNDED_CORNER_SLICES - 1; i >= 0; --i) {
glVertex2f(right - radius + radius * glwRoundedCorners[i].x,
top + radius - radius * glwRoundedCorners[i].y);
}
}
static void glwDrawRightBottomVertexs(float left, float top, float right,
float bottom, float radius) {
int i;
for (i = 0; i < GLW_SMALL_ROUNDED_CORNER_SLICES; ++i) {
glVertex2f(right - radius + radius * glwRoundedCorners[i].x,
bottom - radius + radius * glwRoundedCorners[i].y);
}
}
static void glwDrawLeftBottomVertexs(float left, float top, float right,
float bottom, float radius) {
int i;
for (i = GLW_SMALL_ROUNDED_CORNER_SLICES - 1; i >= 0; --i) {
glVertex2f(left + radius - radius * glwRoundedCorners[i].x,
bottom - radius + radius * glwRoundedCorners[i].y);
}
}
static void glwDrawLeftTopVertexs(float left, float top, float right,
float bottom, float radius) {
int i;
for (i = 0; i < GLW_SMALL_ROUNDED_CORNER_SLICES; ++i) {
glVertex2f(left + radius - radius * glwRoundedCorners[i].x,
top + radius - radius * glwRoundedCorners[i].y);
}
}
void glwDrawRoundedRectBorder(float x, float y, float width, float height,
float radius, unsigned int color) {
float left = x;
float top = y;
float bottom = y + height - 1;
float right = x + width - 1;
glDisable(GL_TEXTURE_2D);
glColor3f(glwR(color), glwG(color), glwB(color));
glBegin(GL_LINE_LOOP);
glVertex2f(left, top + radius);
glwDrawLeftTopVertexs(left, top, right, bottom, radius);
glVertex2f(left + radius, top);
glVertex2f(right - radius, top);
glwDrawRightTopVertexs(left, top, right, bottom, radius);
glVertex2f(right, top + radius);
glVertex2f(right, bottom - radius);
glwDrawRightBottomVertexs(left, top, right, bottom, radius);
glVertex2f(right - radius, bottom);
glVertex2f(left + radius, bottom);
glwDrawLeftBottomVertexs(left, top, right, bottom, radius);
glVertex2f(left, bottom - radius);
glEnd();
}
#define PI_2 1.57079632679490f
#define SIN(x) SDL_sinf (x)
#define COS(x) SDL_cosf (x)
typedef struct _g2d_vertex_t g2d_vertex_t;
struct _g2d_vertex_t {
float x, y;
};
// pVertices - destination buffer
// nVertices - buffer size
// dx - width
// dy - height
// r - radius
// returnes the number of used vertices
int
__cdecl buildRoundedRect (g2d_vertex_t * pVertices, int nVertices, float dx, float dy, float r) {
float a, da;
int i1, i2, i3, i4, n;
if (nVertices < 4) { return 0; }
if (nVertices == 4) {
pVertices [0].x = 0.f; pVertices [0].y = 0.f;
pVertices [1].x = dx; pVertices [1].y = 0.f;
pVertices [2].x = dx; pVertices [2].y = dy;
pVertices [3].x = 0.f; pVertices [3].y = dy;
return nVertices;
}
n = nVertices >> 2;
if (r > dx / 2.f) { r = dx / 2.f; }
if (r > dy / 2.f) { r = dy / 2.f; }
a = 0.f;
da = PI_2 / (float) (n - 1);
for (i1 = 0, i2 = (n << 1) - 1, i3 = n << 1, i4 = (n << 2) - 1; i1 < n; i1++, i2--, i3++, i4--, a += da) {
float cosA = COS (a), sinA = SIN (a);
pVertices [i1].x = (dx - r) + r * cosA; pVertices [i1].y = (dy - r) + r * sinA;
pVertices [i2].x = r - r * cosA; pVertices [i2].y = (dy - r) + r * sinA;
pVertices [i3].x = r - r * cosA; pVertices [i3].y = r - r * sinA;
pVertices [i4].x = (dx - r) + r * cosA; pVertices [i4].y = r - r * sinA;
}
return n << 2;
}
void drawRoundedRect () {
g2d_vertex_t vertices [50];
glColor3f (0.3f, 0.5f, 0.2f);
glVertexPointer (2, GL_FLOAT, 0, vertices);
glEnableClientState (GL_VERTEX_ARRAY);
glDrawArrays (GL_LINE_LOOP, 0, buildRoundedRect (vertices, 50 /* max count of vertices to use: 4 - 50 */, 150.f, 80.f, 20.f));
}
Bit of a bump but I was stuck on the same problem today, this is what I output, its created with Desktop GL, but should be very easy to convert to GLES, everything is a strip. It is probably not as optimized as it should be but if someone want to have a stab at it please be my guest ;)
typedef struct
{
float x;
float y;
} Vector2f;
void RoundRect( int x,
int y,
int width,
int height,
int radius,
int resolution )
{
float step = ( 2.0f * M_PI ) / resolution,
angle = 0.0f,
x_offset,
y_offset;
int i = 0;
unsigned int index = 0,
segment_count = ( int )( resolution / 4 );
Vector2f *top_left = ( Vector2f * ) malloc( segment_count * sizeof( Vector2f ) ),
*bottom_left = ( Vector2f * ) malloc( segment_count * sizeof( Vector2f ) ),
*top_right = ( Vector2f * ) malloc( segment_count * sizeof( Vector2f ) ),
*bottom_right = ( Vector2f * ) malloc( segment_count * sizeof( Vector2f ) ),
bottom_left_corner = { x + radius,
y - height + radius };
while( i != segment_count )
{
x_offset = cosf( angle );
y_offset = sinf( angle );
top_left[ index ].x = bottom_left_corner.x -
( x_offset * radius );
top_left[ index ].y = ( height - ( radius * 2.0f ) ) +
bottom_left_corner.y -
( y_offset * radius );
top_right[ index ].x = ( width - ( radius * 2.0f ) ) +
bottom_left_corner.x +
( x_offset * radius );
top_right[ index ].y = ( height - ( radius * 2.0f ) ) +
bottom_left_corner.y -
( y_offset * radius );
bottom_right[ index ].x = ( width - ( radius * 2.0f ) ) +
bottom_left_corner.x +
( x_offset * radius );
bottom_right[ index ].y = bottom_left_corner.y +
( y_offset * radius );
bottom_left[ index ].x = bottom_left_corner.x -
( x_offset * radius );
bottom_left[ index ].y = bottom_left_corner.y +
( y_offset * radius );
top_left[ index ].x = roundf( top_left[ index ].x );
top_left[ index ].y = roundf( top_left[ index ].y );
top_right[ index ].x = roundf( top_right[ index ].x );
top_right[ index ].y = roundf( top_right[ index ].y );
bottom_right[ index ].x = roundf( bottom_right[ index ].x );
bottom_right[ index ].y = roundf( bottom_right[ index ].y );
bottom_left[ index ].x = roundf( bottom_left[ index ].x );
bottom_left[ index ].y = roundf( bottom_left[ index ].y );
angle -= step;
++index;
++i;
}
glBegin( GL_TRIANGLE_STRIP );
{
// Top
{
i = 0;
while( i != segment_count )
{
//glColor4f( 1.0f, 0.0f, 0.0f, 1.0f );
glColor4f( 0.0f, 0.0f, 0.0f, 1.0f );
glVertex2i( top_left[ i ].x,
top_left[ i ].y );
//glColor4f( 0.0f, 1.0f, 0.0f, 1.0f );
glColor4f( 0.0f, 0.0f, 0.0f, 1.0f );
glVertex2i( top_right[ i ].x,
top_right[ i ].y );
++i;
}
}
// In order to stop and restart the strip.
glColor4f( 0.0f, 1.0f, 0.0f, 1.5f );
glVertex2i( top_right[ 0 ].x,
top_right[ 0 ].y );
glColor4f( 0.0f, 1.0f, 0.0f, 1.5f );
glVertex2i( top_right[ 0 ].x,
top_right[ 0 ].y );
// Center
{
//glColor4f( 0.0f, 1.0f, 0.0f, 1.0f );
glColor4f( 0.0f, 0.0f, 0.0f, 1.0f );
glVertex2i( top_right[ 0 ].x,
top_right[ 0 ].y );
//glColor4f( 1.0f, 0.0f, 0.0f, 1.0f );
glColor4f( 0.0f, 0.0f, 0.0f, 1.0f );
glVertex2i( top_left[ 0 ].x,
top_left[ 0 ].y );
//glColor4f( 0.0f, 0.0f, 1.0f, 1.0f );
glColor4f( 0.5f, 0.5f, 0.5f, 1.0f );
glVertex2i( bottom_right[ 0 ].x,
bottom_right[ 0 ].y );
//glColor4f( 1.0f, 1.0f, 0.0f, 1.0f );
glColor4f( 0.5f, 0.5f, 0.5f, 1.0f );
glVertex2i( bottom_left[ 0 ].x,
bottom_left[ 0 ].y );
}
// Bottom
i = 0;
while( i != segment_count )
{
//glColor4f( 0.0f, 0.0f, 1.0f, 1.0f );
glColor4f( 0.5f, 0.5f, 0.5f, 1.0f );
glVertex2i( bottom_right[ i ].x,
bottom_right[ i ].y );
//glColor4f( 1.0f, 1.0f, 0.0f, 1.0f );
glColor4f( 0.5f, 0.5f, 0.5f, 1.0f );
glVertex2i( bottom_left[ i ].x,
bottom_left[ i ].y );
++i;
}
}
glEnd();
glBegin( GL_LINE_STRIP );
//glColor4f( 0.0f, 1.0f, 1.0f, 1.0f );
glColor4f( 1.0f, 0.5f, 0.0f, 1.0f );
// Border
{
i = ( segment_count - 1 );
while( i > -1 )
{
glVertex2i( top_left[ i ].x,
top_left[ i ].y );
--i;
}
i = 0;
while( i != segment_count )
{
glVertex2i( bottom_left[ i ].x,
bottom_left[ i ].y );
++i;
}
i = ( segment_count - 1 );
while( i > -1 )
{
glVertex2i( bottom_right[ i ].x,
bottom_right[ i ].y );
--i;
}
i = 0;
while( i != segment_count )
{
glVertex2i( top_right[ i ].x,
top_right[ i ].y );
++i;
}
// Close the border.
glVertex2i( top_left[ ( segment_count - 1 ) ].x,
top_left[ ( segment_count - 1 ) ].y );
}
glEnd();
glBegin( GL_LINES );
//glColor4f( 0.0f, 1.0f, 1.0f, 1.0f );
glColor4f( 0.0f, 0.5f, 1.0f, 1.0f );
// Separator
{
// Top bar
glVertex2i( top_right[ 0 ].x,
top_right[ 0 ].y );
glVertex2i( top_left[ 0 ].x,
top_left[ 0 ].y );
// Bottom bar
glVertex2i( bottom_left[ 0 ].x,
bottom_left[ 0 ].y );
glVertex2i( bottom_right[ 0 ].x,
bottom_right[ 0 ].y );
}
glEnd();
free( top_left );
free( bottom_left );
free( top_right );
free( bottom_right );
}
To draw the rounded rectangle simply call something like inside an orthographic view:
RoundRect( 200, /* x */
400, /* y */
400, /* width */
300, /* height */
25, /* Corner radius, at least less than 140? */
64 /* need to be "dividable" by 4 */ );
I needed to draw similar rectangle, but transparent - and code above draws some of triangles overlap. Fixed that, also removed malloc, just to simplify solution. Here is my version:
typedef struct
{
float x;
float y;
} Vector2f;
//
// Draws rounded rectangle.
//
// Slightly tuned version of http://stackoverflow.com/questions/5369507/opengles-1-0-2d-rounded-rectangle
//
#define ROUNDING_POINT_COUNT 8 // Larger values makes circle smoother.
void DrawRoundRect( float x, float y, float width, float height, float* color = 0, float radius = 0.0 )
{
Vector2f top_left[ROUNDING_POINT_COUNT];
Vector2f bottom_left[ROUNDING_POINT_COUNT];
Vector2f top_right[ROUNDING_POINT_COUNT];
Vector2f bottom_right[ROUNDING_POINT_COUNT];
if( radius == 0.0 )
{
radius = min(width, height);
radius *= 0.10; // 10%
}
int i = 0;
float x_offset, y_offset;
float step = ( 2.0f * pi ) / (ROUNDING_POINT_COUNT * 4),
angle = 0.0f;
unsigned int index = 0, segment_count = ROUNDING_POINT_COUNT;
Vector2f bottom_left_corner = { x + radius, y - height + radius };
while( i != segment_count )
{
x_offset = cosf( angle );
y_offset = sinf( angle );
top_left[ index ].x = bottom_left_corner.x -
( x_offset * radius );
top_left[ index ].y = ( height - ( radius * 2.0f ) ) +
bottom_left_corner.y -
( y_offset * radius );
top_right[ index ].x = ( width - ( radius * 2.0f ) ) +
bottom_left_corner.x +
( x_offset * radius );
top_right[ index ].y = ( height - ( radius * 2.0f ) ) +
bottom_left_corner.y -
( y_offset * radius );
bottom_right[ index ].x = ( width - ( radius * 2.0f ) ) +
bottom_left_corner.x +
( x_offset * radius );
bottom_right[ index ].y = bottom_left_corner.y +
( y_offset * radius );
bottom_left[ index ].x = bottom_left_corner.x -
( x_offset * radius );
bottom_left[ index ].y = bottom_left_corner.y +
( y_offset * radius );
top_left[ index ].x = top_left[ index ].x;
top_left[ index ].y = top_left[ index ].y;
top_right[ index ].x = top_right[ index ].x;
top_right[ index ].y = top_right[ index ].y;
bottom_right[ index ].x = bottom_right[ index ].x ;
bottom_right[ index ].y = bottom_right[ index ].y;
bottom_left[ index ].x = bottom_left[ index ].x ;
bottom_left[ index ].y = bottom_left[ index ].y ;
angle -= step;
++index;
++i;
}
static GLubyte clr[] = { 156, 207, 255, 128 }; // Light blue, 50% transparent.
if( color )
glColor4fv(color);
else
glColor4ubv(clr);
glBegin( GL_TRIANGLE_STRIP );
{
// Top
for( i = segment_count - 1 ; i >= 0 ; i--)
{
glVertex2f( top_left[ i ].x, top_left[ i ].y );
glVertex2f( top_right[ i ].x, top_right[ i ].y );
}
// In order to stop and restart the strip.
glVertex2f( top_right[ 0 ].x, top_right[ 0 ].y );
glVertex2f( top_right[ 0 ].x, top_right[ 0 ].y );
// Center
glVertex2f( top_right[ 0 ].x, top_right[ 0 ].y );
glVertex2f( top_left[ 0 ].x, top_left[ 0 ].y );
glVertex2f( bottom_right[ 0 ].x, bottom_right[ 0 ].y );
glVertex2f( bottom_left[ 0 ].x, bottom_left[ 0 ].y );
// Bottom
for( i = 0; i != segment_count ; i++ )
{
glVertex2f( bottom_right[ i ].x, bottom_right[ i ].y );
glVertex2f( bottom_left[ i ].x, bottom_left[ i ].y );
}
}
glEnd();
} //DrawRoundRect