C++
Tu primera ventana
Guía línea por línea para crear tu primera ventana con Dear ImGui — qué hace cada llamada, qué devuelve, y si es obligatoria u opcional.
Requisitos previos
Antes de escribir la primera ventana con Dear ImGui necesitas tener listo el proyecto en C++ con:
- Un compilador C++17 o superior — si todavía no tienes uno, la página de instalación de g++ lo cubre para Windows, macOS y Linux.
- El código fuente de Dear ImGui (carpeta
imgui/con el core y los backends). - Una biblioteca de ventana/entrada: en esta guía usamos GLFW.
- Una API gráfica: en esta guía usamos OpenGL 3.
Si aún no tienes claro qué es C++ ni por qué merece la pena aprenderlo antes de meterte con ImGui, échale un vistazo a ¿Por qué aprender C++ en 2026? en la sección de fundamentos.
Estructura general de una aplicación con ImGui
Toda aplicación con Dear ImGui sigue el mismo esqueleto, independientemente del backend elegido:
- Crear una ventana nativa (con GLFW, SDL, Win32…).
- Crear el contexto de ImGui (
ImGui::CreateContext()). - Inicializar los backends de plataforma y de renderizado.
- Ejecutar el bucle principal: nuevo frame, describir la UI, renderizar.
- Liberar los recursos al cerrar la aplicación.
Esta página va a desarmar cada uno de esos cinco pasos línea por línea: qué hace exactamente cada llamada, qué devuelve (y por qué te importa o no te importa ese valor), y si es obligatoria o solo una opción de configuración. La idea no es que copies el bloque final sin pensarlo, sino que entiendas cada pieza lo bastante bien como para poder escribirla de memoria.
Los fragmentos de código de esta sección muestran cada paso por separado. Ninguno compila solo: son piezas de un mismo programa. La versión completa, copiable y compilable tal cual, está al final de la página en Programa completo.
Dos formas distintas de nombrar funciones, y por qué
Antes de entrar en la primera línea, una aclaración que evita confusión durante toda la página: vas a ver dos estilos de nombres muy distintos.
ImGui::CreateContext(),ImGui::NewFrame(),ImGui::Begin()… — funciones dentro del namespaceImGui. Este es el core de la librería: el motor que decide qué widgets existen, cómo se comportan y qué hay que dibujar. Es C++ normal, con el mismo::de namespace que ya usas constd::—ImGui::CreateContext()se lee igual questd::cout: “la funciónCreateContext, dentro del namespaceImGui”.ImGui_ImplGlfw_InitForOpenGL(),ImGui_ImplOpenGL3_NewFrame()… — funciones con el prefijoImGui_Impl, sin namespace. Estos son los backends: los archivosimgui_impl_*.cppque conectan el core (que no sabe nada de GLFW ni de OpenGL) con una ventana y una API gráfica concretas. Se escriben así, como funciones sueltas con prefijo en vez de dentro de un namespace, porque están pensados para copiarse y pegarse directamente dentro de tu proyecto y modificarse si hace falta — no son parte “sellada” de la librería como el core.
Esta distinción no es cosmética: te dice de un vistazo si una función pertenece a la lógica de la interfaz (ImGui::, la usarías igual sin importar si el backend es GLFW o SDL) o a la conexión con tu ventana/GPU concreta (ImGui_Impl*, cambia si cambias de backend).
Paso a paso: tu primera ventana
1. IMGUI_CHECKVERSION()
IMGUI_CHECKVERSION();No es una función, es una macro: en tiempo de compilación, compara la versión de los archivos de cabecera (imgui.h) que estás incluyendo contra el tamaño de una estructura interna de la biblioteca ya compilada. Si alguna vez mezclas por error un imgui.h de una versión con un imgui.cpp compilado de otra (típico si actualizas solo parte de los archivos), esta macro lo detecta aquí y falla de forma clara — en vez de que el programa compile pero se comporte raro o crashee más adelante, en un punto mucho más difícil de rastrear.
¿Obligatoria? No técnicamente (el programa compila y corre sin ella), pero se considera obligatoria en la práctica: es gratis, no hace nada en tiempo de ejecución normal, y evita una categoría entera de bugs difíciles de diagnosticar. Por eso todos los ejemplos oficiales de ImGui la incluyen como primera línea.
2. ImGui::CreateContext()
ImGui::CreateContext();Un contexto es el objeto que guarda todo el estado interno de ImGui: la posición y tamaño de cada ventana abierta, qué widget tiene el foco, los estilos de color activos, las fuentes cargadas, los datos que se van a dibujar este frame… todo. CreateContext() reserva y prepara ese objeto, y lo marca como el contexto “actual”.
Esto explica algo que puede parecer mágico: después de esta línea, llamas a ImGui::Begin(...), ImGui::Text(...), etc. sin pasarles el contexto como parámetro en ningún lado. Lo encuentran solo porque CreateContext() lo dejó guardado como el contexto activo, y todas las funciones del namespace ImGui operan implícitamente sobre “el contexto actual”. Es una variable de estado global oculta a propósito, para que no tengas que arrastrar un puntero a contexto por todo tu código.
¿Qué devuelve? Un ImGuiContext* — un puntero al contexto recién creado. Casi nadie guarda ese valor de retorno (en el ejemplo de arriba se descarta directamente), porque no lo necesitas: como se dijo arriba, las demás funciones ya lo encuentran solas. Solo te haría falta ese puntero en un caso avanzado — manejar varios contextos ImGui a la vez en el mismo programa — que está fuera del alcance de esta página.
¿Obligatoria? Sí, sin excepción. Ninguna otra función de ImGui:: funciona sin un contexto activo.
3. ImGui::GetIO()
ImGuiIO& io = ImGui::GetIO();ImGuiIO es una estructura (parecida a los structs que ya viste, aunque esta la define la propia librería) que centraliza dos cosas: la configuración de ImGui (qué características activar) y los datos de entrada que los backends escriben ahí cada frame (posición del mouse, teclas presionadas, tamaño de la pantalla).
Fíjate en el &: GetIO() devuelve una referencia, no una copia. Esto es intencional y necesario: io no es tu propia variable con datos que tú administras, es un alias directo a la estructura real que vive dentro del contexto. Si GetIO() devolviera una copia, cualquier cambio que hicieras en io se perdería en el aire — modificarías tu copia, no la que ImGui realmente lee cada frame. Con la referencia, escribir io.ConfigFlags = ... modifica exactamente la estructura que ImGui va a consultar.
¿Obligatoria? Solo si necesitas leer o cambiar algo de esa configuración (como el flag del siguiente paso). Si no tocaras nada de io, técnicamente podrías no llamarla — pero en la práctica casi todo programa con ImGui necesita ajustar algo ahí, así que aparece en prácticamente todos los ejemplos.
4. io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;ConfigFlags es un entero donde cada bit individual representa una característica que se puede prender o apagar (a esto se le llama bitmask, “máscara de bits”). ImGuiConfigFlags_NavEnableKeyboard es una constante que tiene un único bit encendido — el que corresponde a “permitir navegar la interfaz con las flechas del teclado, sin mouse”.
|= es el operador de asignación con OR bit a bit (no confundir con ||, el operador lógico que ya conoces de control de flujo — ese trabaja con bool, este trabaja bit por bit sobre un entero). io.ConfigFlags |= X es la forma corta de io.ConfigFlags = io.ConfigFlags | X: enciende el bit de X en ConfigFlags sin apagar ningún otro bit que ya estuviera encendido. Es la manera estándar de “activar una opción más” en una bandera de configuración sin pisar las demás.
¿Obligatoria? No, es puramente opcional. Sin esta línea, ImGui funciona igual, solo que la interfaz no responde a las flechas/tab del teclado como navegación. Se incluye en casi todos los ejemplos porque es una mejora de accesibilidad barata, no porque haga falta para que algo funcione.
5. ImGui::StyleColorsDark()
ImGui::StyleColorsDark();Cambia la paleta de colores activa (fondo de ventanas, texto, bordes, botones…) a un esquema oscuro predefinido, modificando internamente la estructura ImGuiStyle del contexto actual. No devuelve nada (void): su único efecto es ese cambio de estado interno.
¿Obligatoria? No. Existen también ImGui::StyleColorsLight() (tema claro) y ImGui::StyleColorsClassic() (el tema original de ImGui, de los años en que apareció la librería). Si no llamas a ninguna, ImGui arranca con un tema por defecto igualmente utilizable. Es cuestión de gusto, no de funcionamiento.
6. Los dos backends: por qué son dos, no uno
ImGui_ImplGlfw_InitForOpenGL(window, true);
ImGui_ImplOpenGL3_Init("#version 130");Nota que aquí aparecen dos llamadas de inicialización, no una. No es redundancia: son dos responsabilidades completamente distintas, resueltas por dos backends independientes.
-
ImGui_ImplGlfw_InitForOpenGL(window, true)es el backend de plataforma: se engancha a GLFW para recibir eventos de entrada (movimiento del mouse, teclas, scroll) y traducirlos al formato queImGuiIOespera. El primer parámetro (window) es elGLFWwindow*que creaste antes con GLFW — le dice a este backend sobre cuál ventana debe escuchar eventos. El segundo (true) esinstall_callbacks: le pide a la función que instale automáticamente los callbacks de GLFW (mouse, teclado, etc.) por ti. Si pasarasfalse, tendrías que llamar tú mismo a las funciones de reenvío de eventos de ImGui cada vez que ocurra algo — útil solo si tu programa ya tenía sus propios callbacks de GLFW y necesitas combinarlos a mano. -
ImGui_ImplOpenGL3_Init(glsl_version)es el backend de renderizado: prepara los recursos de OpenGL (shaders, buffers de vértices) que se van a usar más adelante para efectivamente dibujar los triángulos que forman los botones, textos y ventanas de ImGui en pantalla. El parámetro es la versión de GLSL (el lenguaje de shaders de OpenGL) a usar, como texto ("#version 130"corresponde a OpenGL 3.0).
¿Por qué separados? Porque ImGui separa a propósito “de dónde vienen los eventos” (plataforma: GLFW, SDL, Win32…) de “cómo se dibuja” (renderizador: OpenGL, DirectX, Vulkan, Metal…). Gracias a esa separación, puedes combinar cualquier backend de plataforma con cualquier backend de renderizado — por ejemplo, GLFW + Vulkan es una combinación tan válida como GLFW + OpenGL3 — sin que el core de ImGui necesite saber nada de ninguno de los dos.
¿Obligatorios? Sí, ambos. Sin el de plataforma, ImGui nunca se entera de que moviste el mouse. Sin el de renderizado, ImGui::Render() prepara los datos a dibujar pero nada los convierte en píxeles.
7. El bucle principal, línea por línea
Este es el corazón de una aplicación en modo inmediato: en cada vuelta del bucle se describe la interfaz entera desde cero, y el orden de las líneas no es arbitrario — cada una depende de que la anterior ya haya corrido.
while (!glfwWindowShouldClose(window)) {
glfwPollEvents();
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
ImGui::Begin("Mi primera ventana");
ImGui::Text("¡Hola desde Dear ImGui!");
ImGui::End();
ImGui::Render();
glClear(GL_COLOR_BUFFER_BIT);
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); // sin esto, pantalla en negro
glfwSwapBuffers(window);
}glfwWindowShouldClose(window) — pura GLFW, nada de ImGui todavía: revisa si el sistema operativo pidió cerrar la ventana (el usuario tocó la X, por ejemplo) y devuelve true en ese caso, lo que corta el while.
glfwPollEvents() — le pide al sistema operativo los eventos pendientes de esta ventana (movimientos de mouse, teclas, redimensionar) y los despacha a quien los esté escuchando — que, gracias al true que le pasaste a ImGui_ImplGlfw_InitForOpenGL, incluye automáticamente a ImGui. Sin esta línea, la ventana se ve congelada: no reacciona a nada.
ImGui_ImplOpenGL3_NewFrame() y ImGui_ImplGlfw_NewFrame() — cada backend hace su propio mantenimiento de inicio de frame: el de OpenGL3 se asegura de que sus recursos gráficos (como la textura de fuentes) estén listos; el de GLFW calcula el tiempo transcurrido desde el frame anterior y actualiza en ImGuiIO la posición del mouse y el tamaño actual de la ventana. Deben llamarse antes que ImGui::NewFrame(), porque este último ya asume que ImGuiIO tiene los datos frescos de este frame.
ImGui::NewFrame() — le dice al core de ImGui “empieza a describir un frame nuevo”: limpia el estado temporal del frame anterior y queda lista para recibir llamadas a Begin/Text/etc. Ninguna función de widgets (Begin, Text, Button…) puede llamarse antes de esta línea ni después de ImGui::Render().
ImGui::Begin("Mi primera ventana") — abre una ventana con ese título. Todo lo que llames entre este Begin y su End correspondiente se dibuja dentro de esa ventana. Devuelve un bool: true si la ventana está actualmente expandida y visible, false si el usuario la minimizó/colapsó (por ejemplo, haciendo clic en la flechita del título). Ese valor de retorno es una optimización: si vas a hacer cálculos caros para decidir qué mostrar, puedes envolver el contenido en if (ImGui::Begin(...)) { ... } y saltarte ese trabajo cuando la ventana ni siquiera se ve. Este ejemplo lo ignora a propósito por simplicidad — con una sola línea de Text no vale la pena la optimización — pero lo vas a ver usado así en ejemplos más avanzados.
ImGui::Text("¡Hola desde Dear ImGui!") — encola un widget de texto simple dentro de la ventana actualmente abierta. No devuelve nada útil (void): un texto no se “interactúa”, solo se muestra.
ImGui::End() — cierra la ventana abierta por Begin. A diferencia de otras funciones de la librería, End() se llama siempre, incluso si Begin() devolvió false — no es opcional ni condicional al valor de retorno. Begin/End deben aparecer siempre en pares, exactamente uno de cada uno por ventana.
ImGui::Render() — toma todos los Begin/Text/Button/etc. que llamaste desde NewFrame() hasta ahora y los convierte en una estructura de datos lista para dibujar (vértices, índices, comandos de dibujo) — pero no dibuja nada en pantalla todavía. Es una separación deliberada entre “describir la interfaz” y “convertirla en algo que la GPU pueda pintar”.
glClear(GL_COLOR_BUFFER_BIT) — otra vez OpenGL puro, no ImGui: borra el contenido del color buffer (lo que se veía hasta ahora) para no dibujar el frame nuevo encima de la basura del anterior. Sin esto, en algunas configuraciones ves parpadeos o restos de frames viejos.
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()) — dos llamadas en una línea, de adentro hacia afuera:
ImGui::GetDrawData()devuelve un puntero (ImDrawData*) a los datos queImGui::Render()acaba de generar — es la forma de “recoger” el resultado deRender(), ya queRender()en sí no devuelve nada.ImGui_ImplOpenGL3_RenderDrawData(...)recibe ese puntero y ahí sí emite las llamadas reales de OpenGL que dibujan los triángulos en la pantalla.
Esta línea es la que se salta con más frecuencia al integrar ImGui por primera vez, y el síntoma es una pantalla en negro sin ningún error explícito: todo lo anterior compiló y corrió bien, pero nadie le pidió a la GPU que dibujara los datos que Render() preparó.
glfwSwapBuffers(window) — GLFW usa doble buffer: mientras se ve una imagen en pantalla (el front buffer), tu programa dibuja la siguiente en un buffer invisible aparte (el back buffer, donde acaban de caer los triángulos de ImGui). SwapBuffers intercambia cuál de los dos se muestra, de forma instantánea. Sin esto, jamás verías nada de lo que dibujaste: quedaría siempre en el buffer invisible.
Liberando los recursos
Al cerrar la aplicación hay que destruir los backends y el contexto en orden inverso a como se crearon:
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();
ImGui::DestroyContext();El orden no es casualidad: primero se creó el contexto, después el backend de plataforma, después el de renderizado — y al cerrar se deshace exactamente al revés (renderizado, plataforma, contexto). La razón es que los dos backends, durante su propio apagado, todavía necesitan consultar datos que viven dentro del contexto (por ejemplo, para liberar correctamente los recursos que registraron ahí). Si destruyeras el contexto primero, esos backends intentarían leer un contexto que ya no existe. Es el mismo principio de “lo último que se creó es lo primero que se destruye” que vas a reconocer si más adelante estudias estructuras de datos tipo pila.
Con esto ya tienes una ventana funcional de Dear ImGui integrada en un bucle de render con GLFW y OpenGL3. A partir de aquí, todo lo demás (sliders, tablas, árboles, menús) se construye añadiendo más llamadas dentro del bloque Begin()/End() — el resto del “esqueleto” (contexto, backends, bucle) que acabas de entender en detalle no vuelve a cambiar.
Programa completo
Uniendo todos los fragmentos anteriores, más la creación de la ventana con GLFW (que no se detalló arriba porque es GLFW puro, no específico de ImGui), este es el programa completo:
#include <GLFW/glfw3.h>
#include "imgui.h"
#include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.h"
int main() {
if (!glfwInit()) {
return 1; // no se pudo inicializar GLFW
}
const char* glsl_version = "#version 130";
GLFWwindow* window = glfwCreateWindow(1280, 720, "Mi primera ventana ImGui", nullptr, nullptr);
if (window == nullptr) {
glfwTerminate();
return 1;
}
glfwMakeContextCurrent(window);
glfwSwapInterval(1); // v-sync
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
ImGui::StyleColorsDark();
ImGui_ImplGlfw_InitForOpenGL(window, true);
ImGui_ImplOpenGL3_Init(glsl_version);
while (!glfwWindowShouldClose(window)) {
glfwPollEvents();
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
ImGui::Begin("Mi primera ventana");
ImGui::Text("¡Hola desde Dear ImGui!");
ImGui::End();
ImGui::Render();
glClearColor(0.15f, 0.15f, 0.18f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
glfwSwapBuffers(window);
}
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();
ImGui::DestroyContext();
glfwDestroyWindow(window);
glfwTerminate();
return 0;
}Dos líneas que no se explicaron arriba porque son GLFW puro, para cerrar el círculo: glfwMakeContextCurrent(window) le dice a GLFW que las siguientes operaciones gráficas van dirigidas a esta ventana (necesario si algún día manejas más de una), y glfwSwapInterval(1) activa v-sync (sincroniza el SwapBuffers con la tasa de refresco del monitor, para evitar tearing visual).
Para compilarlo hace falta enlazar GLFW, OpenGL, y los archivos fuente de ImGui (el core y los dos backends que estás usando). Un comando representativo en Linux, suponiendo que imgui/ está junto a tu archivo (ajusta las rutas si la tuya es distinta):
g++ -std=c++17 main.cpp \
imgui/imgui.cpp imgui/imgui_draw.cpp imgui/imgui_tables.cpp imgui/imgui_widgets.cpp \
imgui/backends/imgui_impl_glfw.cpp imgui/backends/imgui_impl_opengl3.cpp \
-I imgui -I imgui/backends \
-lglfw -lGL -ldl -o ventana
./ventanaEste comando cambia según el sistema operativo (en Windows normalmente se enlaza contra opengl32 en vez de GL, y GLFW suele instalarse distinto según el compilador) y crece rápido a medida que el proyecto tiene más archivos. Por eso, en la práctica, casi ningún proyecto con ImGui compila así “a mano”: se usa un sistema de build como CMake para que compile igual en cualquier plataforma. El comando de arriba sirve para entender qué piezas hacen falta, no como flujo de trabajo real.
Al ejecutar ./ventana, se abre una ventana nativa de 1280×720 con un panel de ImGui adentro que dice “¡Hola desde Dear ImGui!”. Cerrar la ventana termina el bucle y el programa libera todo en orden antes de salir.
Si llegaste hasta acá entendiendo el porqué de cada línea, ya no necesitas volver a copiar este bloque de memoria: sabes qué reemplazar si cambias de backend, qué borrar si no necesitas una característica opcional, y qué nunca puede faltar.