Integração do ImGui com DirectX11

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 e ImGui::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), onde Nome é o texto exibido e variavelBooleana é a variável booleana que será alterada ao marcar/desmarcar.
  • Para criar um botão, utilize ImGui::Button("NomeBotao"), onde NomeBotao é o texto exibido no botão.
  • Para criar um controle deslizante, use ImGui::SliderFloat("Nome", &valor, valorMinimo, valorMaximo), onde Nome é o texto exibido, valor é o valor a ser modificado, valorMinimo e valorMaximo são os limites do controle.
  • Para editar cores, use ImGui::ColorEdit3("Nome", &cor[0]), onde Nome é o texto exibido e cor[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, passando ImGuiMouseButton_Left verifica o botão esquerdo. Se estiver arrastando, retorna true.
  • Use ImGui::IsKeyPressed() para detectar pressionamento de teclas, retornando true se 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, retornando true nesse 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 ImGuiIO para 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/PopID para 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".

Tags: DirectX11 ImGui C++ desenvolvimento gráficos

Publicado em 10-3 11:01