OpenGL - ES 1.0 2d прямоугольник с закругленными углами

Как сделать скругленный прямоугольник в OpenGL или любой многоугольник с закругленными углами?

6 ответов

Решение

Использование полигонов

Если использование полигонов абсолютно необходимо, например, если объекты с округлением необходимо масштабировать или масштабировать много или если необходимо контролировать количество округлений, можно разбить прямоугольник на несколько подобъектов.

Закругленные углы

Есть как минимум три прямоугольные части и четыре угла. Расчет угловых координат очень прост. Просто найдите точку из круга и постройте треугольники, как на картинке выше.

 float anglerad = PI * angle / 180.0f;
 float x = sinf(anglerad) * radius; 
 float y = cosf(anglerad) * radius;

У этого все еще будут острые края, но больше точек делает углы более круглыми. Маленьким объектам нужно меньше очков, чем крупным.

Простой маршрут - использовать GL_TRIANGLE_FAN для углов. Однако с OpenGL ES может быть целесообразно минимизировать количество вызовов OpenGL и просто использовать больше вершин, так как можно построить целый объект как GL_TRIANGLE_STRIP.

Этот подход можно использовать с любой формой. В случае прямоугольника угол поворота всегда равен 90 градусам, но для других форм угол необходимо рассчитывать по краям.

Использование текстур

Другой подход называется 9-фрагментным масштабированием. Прямоугольник и текстура разбиты на 9 кусочков. Фактическое округление в углу текстуры. Идея состоит в том, что углы не масштабируются, а сохраняют свой первоначальный размер. Этот подход является широко используемым шаблоном в UI-дизайне, допускающем UI-элементы переменного размера, например кнопки. Его преимущество в том, что для визуализации одного прямоугольника нужны только эти 9 квадов. Но это будет выглядеть плохо, если также нужно масштабировать углы, особенно если текстура имеет низкое разрешение.

Немного затруднительно, но я застрял на той же проблеме сегодня, это то, что я вывел, он создан с помощью Desktop GL, но его должно быть очень легко преобразовать в GLES, все является полосой. Это, вероятно, не так оптимизировано, как должно быть, но если кто-то хочет нанести удар, пожалуйста, будьте моим гостем;)

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

Чтобы нарисовать прямоугольник со скругленными углами, просто назовите что-то вроде внутри орфографического представления:

RoundRect( 200, /* x */
           400, /* y */
           400, /* width */
           300, /* height */
           25,  /* Corner radius, at least less than 140? */
           64  /* need to be "dividable" by 4 */ );

Следующий код копируется из моего собственного проекта, я добавил несколько комментариев для объяснения в коде. Если нарисует градиент закругленный прямоугольник без рамки.

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

Если вы хотите нарисовать границу, вот код.

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

Мне нужно было нарисовать похожий прямоугольник, но прозрачный - и код выше рисует, что некоторые треугольники перекрываются. Исправлено это, а также удален malloc, просто для упрощения решения. Вот моя версия:

typedef struct
{
    float x;
    float y;
} Vector2f;

//
//  Draws rounded rectangle.
//
//  Slightly tuned version of http://stackru.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

Я сталкивался с этим исправлением сбоя в некотором программном обеспечении с открытым исходным кодом - версия без GL работала нормально, но в основном предполагалось реализовать закругленный прямоугольник, но разработчик был слишком ленив для этого и решил вместо этого вызвать принудительный сбой:-(

Хотя я думаю, что ответ @vime является лаконичным и полным, я видел много подобных примеров, ни один из которых не давал мне никакой уверенности, и что я думал, что они неочевидны, так что вот для справки... вызывающая функция реализует 4 углы (фрагмент кода)...

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

... и функция сечения дуги DrawGLRoundedCorner(). Обратите внимание, что это предполагает, что glBegin() уже был вызван, и вычерчивает как начало, так и конец дуги - вот почему вам не нужно явно добавлять вершины в конце сторон.

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

Используя другой glBegin, такой как GL_LINE_LOOP, я думаю, что вы получите незаполненный скругленный прямоугольник. Для больших угловых радиусов может потребоваться использовать различные подсказки сглаживания или тому подобное, чтобы они выглядели красивее, но есть и другие сообщения, касающиеся этого.

Надеюсь, что это помогает кому-то.

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

Вы также можете сделать треугольники вместо прямоугольников, чтобы скосить края.

Другие вопросы по тегам