在 OpenGL 中创建按钮和图标

问题描述 投票:0回答:2

我正在尝试使用 OpenGL 制作一个类似于 MS Paint 的编辑器,带有面板和所有功能。在哪里可以找到有关如何创建按钮和图标的材料?

P.S - 我确实发现了提供此类实体的 GLUI,但我不允许使用除 GLUT 之外的任何东西。

如果提供源代码,将会非常有帮助..

user-interface opengl glut
2个回答
0
投票

由于这个问题没有指定语言(OpenGL除外),那么我可以提供与OpenGL相关的信息/建议。

实际上,您需要:

  • 顶点着色器:它将渲染面向屏幕的四边形或图块。
  • 片段着色器:将从图块集纹理渲染图块位置
void main(void) {
   vec2 tile = texture2D( uTilenum, vTexCoord).xy;                  // Choose the specific tile to use from integer texture
   vec2 spriteOffset = floor( tile * 256.0 ) * uTileSize_pix; // Find the distance to the tile corner in pixels
   vec2 spriteCoord = floor( mod( vPixelCoord, uTileSize_pix ) ) + vec2( 0.5, 0.5 );         // Choose the pixel within the tile

   gl_FragColor = uColor * texture2D( uTileset, ( spriteOffset + spriteCoord ) * uInverseTilesetSize_pix );
}
  • 菜单纹理:分为代表您按钮选择的图块集。例如:来自我制作的 Simcity 游戏:https://i.stack.imgur.com/mgjav.jpg
  • 描述菜单的某种结构形式。我个人使用类似于 HTML 的格式,因为它众所周知且清晰。然后,我使用文本解析器解析该结构以创建菜单结构。我将源列为图块集中的“图块编号”。
<button name='button1' align='bottom left' onTouch='switchMovementMode'>
    <img name='btn1img' src='74,75|90,91' width='100' height='100'></img>
</button>
<button name='button2' align='bottom left'>
    <img name='btn2img' src='76,77|92,93' width='100' height='100'></img>
</button>
  • 无论您使用什么语言,用户界面对象都充当菜单元素的集合,并且至少具有 Create、CheckForInteractions 和 Draw 方法。用户界面是每个渲染通道的最后一个绘制,并且仅使用单位矩阵,因此没有透视(即,正交或平面于屏幕)。尽管我循环遍历菜单元素,在每个元素上调用子绘制,但如何绘制是首选。通过 CheckForInteractions,我个人将鼠标/手指位置传递给每个菜单元素以检查触摸/拖动/等,并让菜单元素跟踪其自己的位置。
  • 菜单元素对象,用于跟踪其位置、状态和交互时的响应。如果用户界面是这样设计的,也可以绘制自己。就我而言,我让每个菜单元素在调用 Draw 之前翻译并缩放其 ModelViewProjection 矩阵。例如:通过电话进行交互。
    • touchAction = someTouchMethod();
    • holdAction = someHoldMethod();
    • dragAction = someDragMethod();
  • 可绘制对象(可以与菜单元素组合),用于跟踪绘制四边形或图块所需的数据。我个人选择了带有drawListBuffer的glDrawElements,这意味着你需要:
    • 将四个顶点放入 vertexCooperativeBuffer(Java 示例):
Vec3[] vC = new Vec3[]{
   new Vec3( -1.0f, 1.0f, 0.0f ),   // top left
   new Vec3( -1.0f, -1.0f, 0.0f ),   // bottom left
   new Vec3( 1.0f, -1.0f, 0.0f ),   // bottom right
   new Vec3( 1.0f, 1.0f, 0.0f )    // top right
};
ByteBuffer vertexCoordBuffer = ByteBuffer.allocateDirect( vertexCoord.length * 3 * 4 );
vertexCoordBuffer.order( ByteOrder.nativeOrder() );
for( Vec3 v : vC ){
   vertexCoordBuffer.putFloat( v.x ).putFloat( v.y ).putFloat( v.z );
}

  • 将四个纹理坐标放入纹理坐标缓冲区(Java 示例):
Vec2[] tC= new Vec2[]{
   new Vec2( 0.0f, 0.0f ),  // top left
   new Vec2( 0.0f, 1.0f ),  // bottom left
   new Vec2( 1.0f, 1.0f ),  // bottom right
   new Vec2( 1.0f, 0.0f )  // top right
};
ByteBuffer texCoordBuffer = ByteBuffer.allocateDirect( texCoord.length * 2 * 4 );
texCoordBuffer.order( ByteOrder.nativeOrder() );
for( Vec2 c : Tc ){
   texCoordBuffer.putFloat( c.x ).putFloat( c.y );
}
  • 绘制顺序列表(四边形的两个三角形有 6 项)
int[] drawOrder = new int[]{
   0, 1, 2, 0, 2, 3
};
ByteBuffer dlb = ByteBuffer.allocateDirect( drawOrder.length * 4 );
dlb.order( ByteOrder.nativeOrder() );
drawListBuffer = dlb.asIntBuffer();
drawListBuffer.put( drawOrder );
  • Draw 命令选择已编译的顶点和片段着色器程序,执行属性和统一的所有预渲染分配,然后调用 glDrawElements,如下所示:
glDrawElements( GLES30.GL_TRIANGLES, drawOrder.length, GLES30.GL_UNSIGNED_INT, drawListBuffer );

0
投票

如果您仅使用 C 进行编程,我可以推荐的是 nuklear,它是一个免费的开源 GUI 库,使用起来非常灵活,您甚至可以创建自己的自定义小部件。

初始化库上下文/加载单个字体并将其烘焙到纹理中:

#include <nuklear.h>

/* size of the font */
#define GUI_FONT_SIZE           16

/* ... */

struct nk_context gui_context = {0};
struct nk_font_atlas gui_font_atlas = {0};
struct nk_font *gui_font = NULL;
struct nk_draw_null_texture gui_null_texture = {0};
GLuint gui_font_texture_id = 0;

/* ... */

int initialize_gui(void)
{
    struct nk_font_config cfg = {0};
    const void *pixels = NULL;
    GLint texwidth = 0;
    int w = 0;
    int h = 0;

    /* initialize the font baker */
    nk_font_atlas_init_default(&gui_font_atlas);
    nk_font_atlas_begin(&gui_font_atlas);

    /* set font baker's parameters */
    memset(&cfg, 0, sizeof(cfg));
    cfg.size = (float)GUI_FONT_SIZE;
    cfg.merge_mode = nk_false;
    cfg.pixel_snap = nk_false;
    cfg.oversample_h = 4;
    cfg.oversample_v = 4;
    cfg.range = nk_font_default_glyph_ranges();
    cfg.coord_type = NK_COORD_UV;

    /* add fonts to bake */
    gui_font = nk_font_atlas_add_from_file(&gui_font_atlas,
         "font/DroidSans.ttf", (float)GUI_FONT_SIZE, &cfg);
    if (!gui_font) {
        /* error handling */
    }

    /* bake all the fonts into a single image */
    pixels = nk_font_atlas_bake(&gui_font_atlas, &w, &h,
                        NK_FONT_ATLAS_RGBA32);
    if (!pixels) {
        /* error handling */
    }

    /* create a texture from the baked image and upload it to OpenGL */
    glGenTextures(1, &gui_font_texture_id);
    glBindTexture(GL_TEXTURE_2D, gui_font_texture_id);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA,
                    GL_UNSIGNED_BYTE, pixels);

    /* set texture filtering */
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    /* clamp texture S,T coordinates in a range of 0.0~1.0 */
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    /* release temporary memory used by the font baker */
    nk_font_atlas_end(&gui_font_atlas, nk_handle_id(gui_font_texture_id),
                        &gui_null_texture);
    nk_font_atlas_cleanup(&gui_font_atlas);

    /* initialize nuklear context */
    if (!nk_init_default(&gui_context, &gui_font->handle)) {
        /* error handling */
    }

    /* set default GUI theme */
    nk_style_default(&gui_context);

    /* set GUI font */
    nk_style_set_font(&gui_context, &gui_font->handle);

    /* ... */

    return 1;
}

现在,一旦我们有了有效的核上下文并创建了字体图集纹理,我们就可以创建窗口/按钮/自定义小部件等..

/* creates a window containing a button */

if (nk_begin(ctx, "Window title", nk_rect(
    0, 0, 300, 300), NK_WINDOW_TITLE |
        NK_WINDOW_BORDER | NK_WINDOW_MOVABLE |
        NK_WINDOW_MINIMIZABLE | NK_WINDOW_CLOSABLE |
            NK_WINDOW_NO_SCROLLBAR)) {
    
    nk_layout_row_static(&gui_context, 24, 100, 1);
    if (nk_button_label(&gui_context, "Button")) {
         /* do stuff on click */
    }
}

nk_end(&gui_context);

完成后,您将需要库将您的小部件命令转换为绘制命令(使用对 nk_convert() 的调用),该命令将用于实际绘制所有内容。

/* the maximum number of vertices allocated for use */
#define GUI_MAX_NUM_VERTICES        6000

/* the maximum number of indices allocated for use */
#define GUI_MAX_NUM_INDICES     6000

struct gui_vertex {
    struct vec2 position;   /* vertex position */
    struct vec2 texcoord;   /* vertex texture coordinate */
    struct color color; /* vertex RGBA color */
};

/* ... */

struct nk_buffer vbuf = {0};
struct nk_buffer ibuf = {0};
struct nk_buffer cbuf = {0};
struct nk_convert_config cfg = {0};
struct gui_vertex *vertices = NULL;
unsigned short *indices = NULL;

/* map your OpenGL vertex/index buffers to @vertices/@indices here */

/* initialize nuklear's vertex/index buffers for conversion */
nk_buffer_init_fixed(&vbuf, vertices, sizeof(*vertices) *
                        GUI_MAX_NUM_VERTICES);
nk_buffer_init_fixed(&ibuf, indices, sizeof(*indices) *
                        GUI_MAX_NUM_INDICES);
nk_buffer_init_default(&cbuf);

/* initialize parameters for @nk_convert() */
memset(&cfg, 0, sizeof(cfg));
cfg.vertex_layout = layout;
cfg.vertex_size = sizeof(struct gui_vertex);
cfg.vertex_alignment = NK_ALIGNOF(struct gui_vertex);
cfg.null = gui_null_texture;
cfg.circle_segment_count = 22;
cfg.curve_segment_count = 22;
cfg.arc_segment_count = 22;
cfg.global_alpha = 1.0f;
cfg.shape_AA = NK_ANTI_ALIASING_ON;
cfg.line_AA = NK_ANTI_ALIASING_ON;

nk_convert(&gui_context, &cbuf, &vbuf, &ibuf, &cfg);

/* unmap your OpenGL vertex/index buffers here */

然后你将如何解释这些绘制命令取决于你,你可以使用立即模式绘制它们或者使用缓冲区孤立?如果您使用的是现代 OpenGL。无论如何,语法是相同的,你基本上循环遍历每个绘制命令并解释它:

const nk_draw_index *offset = NULL;

/* ... */

nk_draw_foreach(cmd, &gui_context, &cbuf) {
    /* if the current command doesn't draw anything, skip it */
    if (cmd->elem_count == 0)
        continue;

    /* bind each command's texture */
    glBindTexture(GL_TEXTURE_2D, (GLuint)cmd->texture.id);

    /* set each command's scissor rectangle */
    glScissor((GLint)cmd->clip_rect.x, (GLint)window_client_h -
            (GLint)(cmd->clip_rect.y + cmd->clip_rect.h),
                (GLint)cmd->clip_rect.w,
                    (GLint)cmd->clip_rect.h);

    /* draw each command */
    glDrawElements(GL_TRIANGLES, (GLsizei)cmd->elem_count,
                GL_UNSIGNED_SHORT, offset);

    offset += cmd->elem_count;
}

/* reset and prepare the context for the next frame */
nk_clear(&gui_context);

/* free commands buffer */
nk_buffer_free(&cbuf);

还可以使用此顶点着色器来渲染 GUI:

in vec2 position;
in vec2 texcoord;
in vec4 color;

uniform mat4 transform; // orthographic projection matrix

out vec2 frag_texcoord;
out vec4 frag_colorRGBA;

void main(void)
{
    frag_texcoord = texcoord;
    frag_colorRGBA = color;
    gl_Position = transform * vec4(position, 0.0, 1.0);
}

还有这个片段着色器:

precision highp float;

uniform sampler2D gui_texture;

in vec2 frag_texcoord;
in vec4 frag_colorRGBA;

out vec4 frag_color;    // output fragment color

void main(void)
{
    frag_color = frag_colorRGBA * texture(gui_texture, frag_texcoord);
}

来自 3D 地形编辑器的图片,我也使用 nuklear 库进行编程。

© www.soinside.com 2019 - 2024. All rights reserved.