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