Integração do ImGui com DirectX11
Compilação da Biblioteca Estática do ImGui
Após clonar o projeto ImGui no GitHub, selecione os arquivos principais do ImGui.
Escolha os arquivos necessários na pasta backends conforme a API gráfica e plataforma utilizadas. Neste caso, foram selecionados quatro arquivos específicos.
Crie um novo projeto dentro do seu ambiente de desenvolvimento para incluir esses arquivos. Configure as propriedades do projeto alterando o diretório de saída para $(ProjectDir)$(Platform)\$(Configuration)\ e o tipo de configuração para Biblioteca Estática (.lib). Em seguida, compile o projeto para gerar a biblioteca estática.
A biblioteca gerada ficará localizada em:
- ImGui/x64/Debug/ImGui.lib
- ImGui/x64/Release/ImGui.lib
No seu projeto principle, abra as propriedades e em C/C++ -> Diretórios de Inclusão Adicionais, adicione o caminho do projeto do ImGui.
Em Linker -> Entrada -> Dependências Adicionais, insira o caminho da biblioteca estática gerada.
Inicialização do ImGui
Inclua os cabeçalhos necessários:
#include "imgui.h"
#include "imgui_impl_win32.h"
#include "imgui_impl_dx11.h"
Configure o contexto do ImGui:
bool AplicativoD3D::InicializarImGui()
{
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO(); (void)io;
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Habilita navegação por teclado
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Permite apenas arrastar títulos
// Define estilo do ImGui
ImGui::StyleColorsDark();
//ImGui::StyleColorsLight();
// Configura backends de plataforma/renderizador
ImGui_ImplWin32_Init(janela);
ImGui_ImplDX11_Init(dispositivo_d3d, contexto_d3d);
return true;
}
Chame InicializarImGui em AplicativoD3D::Inicializar:
bool AplicativoD3D::Inicializar()
{
if (!InicializarJanela())
return false;
if (!InicializarDirect3D())
return false;
if (!InicializarImGui())
return false;
return true;
}
Declare o manipulador de mensagens externamente:
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
Adicione o tratamento do ImGui no início da função de processamento de mensagens AplicativoD3D::ProcessarMensagem:
LRESULT AplicativoD3D::ProcessarMensagem(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
if (ImGui_ImplWin32_WndProcHandler(janela_principal, msg, wParam, lParam))
return true;
}
No método Executar(), inicie o framwork do ImGui:
ImGui_ImplDX11_NewFrame();
ImGui_ImplWin32_NewFrame();
ImGui::NewFrame();
Finalmente, em AplicacaoJogo::DesenharCena(), renderize o ImGui:
ImGui::Render();
// A linha abaixo desencadeia o desenho do ImGui no Direct3D
// Portanto, é necessário vincular o buffer traseiro ao pipeline de renderização antes disso
ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());
Uso do ImGui
Uso Básico
- Chamar
ImGui::Begin("NomeJanela")cria uma janela com o nome fornecido eImGui::End()encerra a janela, entre eles podem ser adicionaods elementos:
ImGui::Begin("NomeJanela");
// Funções
ImGui::End();
- Utilize
ImGui::Text("Texto de Exemplo")para adicionar texto à interface. - Para adicionar uma caixa de seleção, use
ImGui::Checkbox("Nome", &variavelBooleana), ondeNomeé o texto exibido evariavelBooleanaé a variável booleana que será alterada ao marcar/desmarcar. - Para criar um botão, utilize
ImGui::Button("NomeBotao"), ondeNomeBotaoé o texto exibido no botão. - Para criar um controle deslizante, use
ImGui::SliderFloat("Nome", &valor, valorMinimo, valorMaximo), ondeNomeé o texto exibido,valoré o valor a ser modificado,valorMinimoevalorMaximosão os limites do controle. - Para editar cores, use
ImGui::ColorEdit3("Nome", &cor[0]), ondeNomeé o texto exibido ecor[0]é o array que armazena a cor.
Controles de Teclado e Mouse
Defina as variáveis necessárias:
static int movimentoX = 0, movimentoY = 0, ajusteEscala = 0, rotacao = 0;
static float angulo1 = 0.0f, angulo2 = 0.0f;
static float angulo3 = 0.0f, angulo4 = 0.0f;
static float escala1 = 1.0f;
static float escala2 = 1.0f;
static float posicaoX1 = 0.0f, posicaoY1 = 0.0f;
static float posicaoX2 = 0.0f, posicaoY2 = 0.0f;
static float campoVisao = XM_PIDIV2;
- Use
ImGui::IsAnyItemActive()para verificar se há interação com a interface:
if (!ImGui::IsAnyItemActive()) {} // Não afeta objetos quando a interface está ativa
- Use
ImGui::IsMouseDragging()para detectar arraste do mouse, por exemplo, passandoImGuiMouseButton_Leftverifica o botão esquerdo. Se estiver arrastando, retornatrue. - Use
ImGui::IsKeyPressed()para detectar pressionamento de teclas, retornandotruese a tecla estiver pressionada. Por exemplo, para mover um objeto ao pressionar W ou S:
if (ImGui::IsKeyPressed(ImGuiKey_W)) { // Mover para cima
movimentoY = 1;
}
if (ImGui::IsKeyPressed(ImGuiKey_S)) { // Mover para baixo
movimentoY = -1;
}
- Use
ImGui::IsKeyReleased()para detectar quando uma tecla é solta, retornandotruenesse caso. Por exemplo, para parar o movimento ao soltar W ou S:
if (ImGui::IsKeyReleased(ImGuiKey_W) || ImGui::IsKeyReleased(ImGuiKey_S)) { // Parar movimento
movimentoY = 0;
}
- Use
ImGuiIOpara obter informações do mouse, como o delta do mouse e a roda do mouse:
ImGuiIO& io = ImGui::GetIO();
auto& delta = io.MouseDelta; // Delta do mouse na atualização
io.MouseWheel; // Valor da roda do mouse
- Use o valor da roda do mouse para implementar zoom, por exemplo:
else if (io.MouseWheel != 0.0f){
escala1 += 0.02f * io.MouseWheel;
if (escala1 > 2.0f)
escala1 = 2.0f;
else if (escala1 < 0.2f)
escala1 = 0.2f;
}
Para controlar dois objetos com suavidade, adicione:
posicaoX2 += movimentoX * 0.0002f; // Atualiza posição X
posicaoY2 += movimentoY * 0.0002f; // Atualiza posição Y
escala2 += ajusteEscala * 0.0002f;
if (escala2 > 2.0f) {
escala2 = 2.0f;
}
else if (escala2 < 0.2f) {
escala2 = 0.2f;
}
angulo3 -= rotacao * 0.0002f;
angulo4 -= rotacao * 0.0002f;
angulo3 = XMScalarModAngle(angulo3);
angulo4 = XMScalarModAngle(angulo4);
Crie um novo buffer de vértices para o segundo objeto:
D3D11_BUFFER_DESC descVertice2;
ZeroMemory(&descVertice2, sizeof(descVertice2));
descVertice2.ByteWidth = sizeof(vertices1);
descVertice2.Usage = D3D11_USAGE_IMMUTABLE;
descVertice2.BindFlags = D3D11_BIND_VERTEX_BUFFER;
descVertice2.CPUAccessFlags = 0;
InitData.pSysMem = vertices1;
HR(dispositivo_d3d->CreateBuffer(&descVertice2, &InitData, bufferVertice2.GetAddressOf()));
E um buffer de índices semelhante:
D3D11_BUFFER_DESC descIndice2;
ZeroMemory(&descIndice2, sizeof(descIndice2));
descIndice2.Usage = D3D11_USAGE_IMMUTABLE;
descIndice2.ByteWidth = sizeof(indices1);
descIndice2.BindFlags = D3D11_BIND_INDEX_BUFFER;
descIndice2.CPUAccessFlags = 0;
InitData.pSysMem = indices1;
HR(dispositivo_d3d->CreateBuffer(&descIndice2, &InitData, bufferIndice2.GetAddressOf()));
Atualize o buffer de constantes no método AplicacaoJogo::AtualizarCena(float dt):
m_BufferConstantes.world = XMMatrixTranspose(
XMMatrixScalingFromVector(XMVectorReplicate(escala1)) *
XMMatrixRotationX(angulo1) * XMMatrixRotationY(angulo2) *
XMMatrixTranslation(posicaoX1, posicaoY1, 0.0f)
);
m_BufferConstantes.proj = XMMatrixTranspose(XMMatrixPerspectiveFovLH(campoVisao, Aspecto(), 1.0f, 1000.0f));
D3D11_MAPPED_SUBRESOURCE dadosMapeados1;
dispositivo_contexto_imediato->Map(bufferConstantes.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &dadosMapeados1);
memcpy_s(dadosMapeados1.pData, sizeof(m_BufferConstantes), &m_BufferConstantes, sizeof(m_BufferConstantes));
dispositivo_contexto_imediato->Unmap(bufferConstantes.Get(), 0);
Vincule os buffers ao contexto do dispositivo:
UINT passos = sizeof(VertexPosColor);
UINT deslocamento = 0;
dispositivo_contexto_imediato->IASetVertexBuffers(0, 1, bufferVertice1.GetAddressOf(), &passos, &deslocamento);
dispositivo_contexto_imediato->IASetIndexBuffer(bufferIndice1.Get(), DXGI_FORMAT_R32_UINT, 0);
dispositivo_contexto_imediato->VSSetConstantBuffers(0, 1, bufferConstantes.GetAddressOf());
dispositivo_contexto_imediato->PSSetConstantBuffers(0, 1, bufferConstantes.GetAddressOf());
Desenhe o primeiro objeto:
dispositivo_contexto_imediato->DrawIndexed(18, 0, 0);
Atualize novamente o buffer de constantes e vincule os buffers do segundo objeto:
m_BufferConstantes.world = XMMatrixTranspose(
XMMatrixScalingFromVector(XMVectorReplicate(escala2)) *
XMMatrixRotationX(angulo3) * XMMatrixRotationY(angulo4) *
XMMatrixTranslation(posicaoX2, posicaoY2, 0.0f)
);
m_BufferConstantes.proj = XMMatrixTranspose(XMMatrixPerspectiveFovLH(campoVisao, Aspecto(), 1.0f, 1000.0f));
D3D11_MAPPED_SUBRESOURCE dadosMapeados2;
dispositivo_contexto_imediato->Map(bufferConstantes.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &dadosMapeados2);
memcpy_s(dadosMapeados2.pData, sizeof(m_BufferConstantes), &m_BufferConstantes, sizeof(m_BufferConstantes));
dispositivo_contexto_imediato->Unmap(bufferConstantes.Get(), 0);
dispositivo_contexto_imediato->IASetVertexBuffers(0, 1, bufferVertice2.GetAddressOf(), &passos, &deslocamento);
dispositivo_contexto_imediato->IASetIndexBuffer(bufferIndice2.Get(), DXGI_FORMAT_R32_UINT, 0);
Desenhe o segundo objeto:
dispositivo_contexto_imediato->DrawIndexed(36, 0, 0);
Identificadores de Componentes
O ImGui utiliza IDs únicos para rastrear componentes, usando o parâmetro Label como ID por padrão. Quando há rótulos iguais, ocorre conflito.
Métodos para resolver conflitos de nomes:
- Use
##para definir IDs únicas. O texto após##não aparece no componente. - Use
PushID/PopIDpara criar escopos de IDs únicos:
ImGui::PushID(0);
if (ImGui::Button("Teste1")) {
::OutputDebugStringA("Botão 1 clicado");
}
ImGui::PopID();
ImGui::PushID(1);
if (ImGui::Button("Teste1")) {
::OutputDebugStringA("Botão 2 clicado");
}
ImGui::PopID();
PushID e PopID devem ser usados em pares, com parâmetros únicos.
Observações:
- Apenas componentes interativos precisam de IDs.
- IDs iguais em janelas diferentes não conflitam.
- Para um rótulo vazio, defina um ID:
ImGui::Button("##id")
- Use
###para evitar que o rótulo seja parte do ID:
sprintf(buf, "Meu jogo (%f FPS)###MeuJogo", fps);
Begin(buf);
O ID real na pilha será "MeuJogo".