前言

Dear ImGui是一个开源GUI框架。除了UI部分外,本身还支持简单的键鼠交互。目前项目内置的是V1.87版本,大概半年时间会更新一次版本,并且对源码有小幅度调整。

注意:直接下载源码使用会导致19章之后的UI效果有误,修改了源码imgui_impl_dx11.cpp,需要用项目源码中的替换下载的。具体原因参考文末

DirectX11 With Windows SDK完整目录

Github项目源码

欢迎加入QQ群: 727623616 可以一起探讨DX11,以及有什么问题也可以在这里汇报。

生成ImGui静态库

方法一:VS建立项目

在本项目的ImGui文件夹中内置了一个VS项目,你可以参考其做法,也可以直接使用它。具体要用到的头文件和源文件包括这些:

如果你是从网上下载的ImGui包,则需要在根目录、backends里面找出上图这些头文件和源文件。

在将文件夹中列出的.cpp和.h拖入自建的ImGui项目后,进入项目属性页进行改动:

部分内容根据自己VS版本进行修改

然后我们需要生成x64 Debug和x64 Release版本的静态库,生成位置分别位于:

  • ImGui/x64/Debug/ImGui.lib
  • ImGui/x64/Release/ImGui.lib

然后打开自己之前新建的项目,在属性页中找到C/C++ → 附加包含目录,添加ImGui的目录进你的项目。

紧接着是链接器 → 输入 → 附加依赖项中直接加入ImGui.lib。在Debug和Release下使用统一使用YourImGuiDir\$(Platform)\$(Configuration)\ImGui.lib,前面是相对或绝对路径均可。

方法二:使用cmake

在ImGui文件夹中已经包含了一个CMakeLists.txt:

cmake_minimum_required(VERSION 3.14)

aux_source_directory(. IMGUI_DIR_SRCS)
add_library(ImGui STATIC ${IMGUI_DIR_SRCS}) target_include_directories(ImGui PUBLIC .)

你可以将ImGui项目文件夹复制到你的项目路径内,然后在你项目的CMakeList.txt加上这样一句话就可以在你的解决方案中添加并使用ImGui库了:

# ImGui
target_link_libraries(YourTargetName ImGui)

初始化ImGui

D3DApp.h中添加这三个头文件

#include <imgui.h>
#include <imgui_impl_dx11.h>
#include <imgui_impl_win32.h>

添加D3DApp::InitImGui方法:

bool D3DApp::InitImGui()
{
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // 允许键盘控制
io.ConfigWindowsMoveFromTitleBarOnly = true; // 仅允许标题拖动 // 设置Dear ImGui风格
ImGui::StyleColorsDark(); // 设置平台/渲染器后端
ImGui_ImplWin32_Init(m_hMainWnd);
ImGui_ImplDX11_Init(m_pd3dDevice.Get(), m_pd3dImmediateContext.Get()); return true; }

D3DApp::Init中调用InitImGui

bool D3DApp::Init()
{
if (!InitMainWindow())
return false; if (!InitDirect3D())
return false; if (!InitImGui())
return false; return true;
}

然后在D3DApp.cpp的上方添加一句话来引用外部函数:

extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

在消息处理函数D3DApp::MsgProc的开头添加ImGui的处理:

LRESULT D3DApp::MsgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
if (ImGui_ImplWin32_WndProcHandler(m_hMainWnd, msg, wParam, lParam))
return true; // ...
}

D3DApp::Run()中,我们插入这三个函数用于启动ImGui新一帧的记录与绘制:

int D3DApp::Run()
{
MSG msg = { 0 }; m_Timer.Reset(); while (msg.message != WM_QUIT)
{
if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
m_Timer.Tick(); if (!m_AppPaused)
{
CalculateFrameStats();
// 这里添加
ImGui_ImplDX11_NewFrame();
ImGui_ImplWin32_NewFrame();
ImGui::NewFrame();
// --------
UpdateScene(m_Timer.DeltaTime());
DrawScene();
}
else
{
Sleep(100);
}
}
} return (int)msg.wParam;
}

最后就是在GameApp::DrawScene()中插入这两句:

void GameApp::UpdateScene(float dt)
{
// 可以在这之前调用ImGui的UI部分
} void GameApp::DrawScene()
{ // 可以在这之前调用ImGui的UI部分
// Direct3D 绘制部分 ImGui::Render();
// 下面这句话会触发ImGui在Direct3D的绘制
// 因此需要在此之前将后备缓冲区绑定到渲染管线上
ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData()); HR(m_pSwapChain->Present(0, 0));
}

这样就完成了ImGui的初始化。接下来可以在UpdateScene里面放几个ImGui的实例窗口看看:

void GameApp::UpdateScene(float dt)
{
// ImGui内部示例窗口
ImGui::ShowAboutWindow();
ImGui::ShowDemoWindow();
ImGui::ShowUserGuide();
}

出现这些窗口且可以操作的话就是成功了,但是在前面的代码中已经禁用了双击窗口区域折叠的功能。

需要注意的是程序运行后,exe所在的路径会生成一个imgui.ini的文件,用于记录窗口的布局,这样下次打开的话就会保持先前的窗口状态。可以在将布局弄好后,把imgui.ini复制复制到项目路径,然后通过cmake复制过去:

file(COPY imgui.ini DESTINATION ${CMAKE_CURRENT_BINARY_DIR})

项目窗口实例

通常一个窗口的结构为:

static bool is_open = true;
if (ImGui::Begin("Window" /*, &is_open */)) // 窗口右上角加上X关闭
{
// 添加组件
}
ImGui::end();

以本项目的为例:

//
// 自定义窗口与操作
//
static float tx = 0.0f, ty = 0.0f, phi = 0.0f, theta = 0.0f, scale = 1.0f, fov = XM_PIDIV2;
static bool animateCube = true, customColor = false;
if (animateCube)
{
phi += 0.3f * dt, theta += 0.37f * dt;
phi = XMScalarModAngle(phi);
theta = XMScalarModAngle(theta);
} if (ImGui::Begin("Use ImGui"))
{
ImGui::Checkbox("Animate Cube", &animateCube); // 复选框
ImGui::SameLine(0.0f, 25.0f); // 下一个控件在同一行往右25像素单位
if (ImGui::Button("Reset Params")) // 按钮
{
tx = ty = phi = theta = 0.0f;
scale = 1.0f;
fov = XM_PIDIV2;
}
ImGui::SliderFloat("Scale", &scale, 0.2f, 2.0f); // 拖动控制物体大小 ImGui::Text("Phi: %.2f degrees", XMConvertToDegrees(phi)); // 显示文字,用于描述下面的控件
ImGui::SliderFloat("##1", &phi, -XM_PI, XM_PI, ""); // 不显示控件标题,但使用##来避免标签重复
// 空字符串避免显示数字
ImGui::Text("Theta: %.2f degrees", XMConvertToDegrees(theta));
// 另一种写法是ImGui::PushID(2);
// 把里面的##2删去
ImGui::SliderFloat("##2", &theta, -XM_PI, XM_PI, "");
// 然后加上ImGui::PopID(2); ImGui::Text("Position: (%.1f, %.1f, 0.0)", tx, ty); ImGui::Text("FOV: %.2f degrees", XMConvertToDegrees(fov));
ImGui::SliderFloat("##3", &fov, XM_PIDIV4, XM_PI / 3 * 2, ""); if (ImGui::Checkbox("Use Custom Color", &customColor))
m_CBuffer.useCustomColor = customColor;
// 下面的控件受上面的复选框影响
if (customColor)
{
ImGui::ColorEdit3("Color", reinterpret_cast<float*>(&m_CBuffer.color)); // 编辑颜色
}
}
ImGui::End();

然后就可以得到这样的一个窗口:

为了让立方体显示自己设置的颜色,着色器改为了下面这样:

// Cube.hlsli
cbuffer ConstantBuffer : register(b0)
{
matrix g_World; // matrix可以用float4x4替代。不加row_major的情况下,矩阵默认为列主矩阵,
matrix g_View; // 可以在前面添加row_major表示行主矩阵
matrix g_Proj; // 该教程往后将使用默认的列主矩阵,但需要在C++代码端预先将矩阵进行转置。
vector g_Color;
uint g_UseCustomColor;
} struct VertexIn
{
float3 posL : POSITION;
float4 color : COLOR;
}; struct VertexOut
{
float4 posH : SV_POSITION;
float4 color : COLOR;
}; // Cube_VS.hlsl
VertexOut VS(VertexIn vIn)
{
VertexOut vOut;
vOut.posH = mul(float4(vIn.posL, 1.0f), g_World); // mul 才是矩阵乘法, 运算符*要求操作对象为
vOut.posH = mul(vOut.posH, g_View); // 行列数相等的两个矩阵,结果为
vOut.posH = mul(vOut.posH, g_Proj); // Cij = Aij * Bij
vOut.color = vIn.color; // 这里alpha通道的值默认为1.0
return vOut;
} // Cube_PS.hlsl
// 像素着色器
float4 PS(VertexOut pIn) : SV_Target
{
return g_UseCustomColor ? g_Color : pIn.color;
}

然后常量缓冲区记得更新:

m_CBuffer.world = XMMatrixTranspose(
XMMatrixScalingFromVector(XMVectorReplicate(scale)) *
XMMatrixRotationX(phi) * XMMatrixRotationY(theta) *
XMMatrixTranslation(tx, ty, 0.0f));
m_CBuffer.proj = XMMatrixTranspose(XMMatrixPerspectiveFovLH(fov, AspectRatio(), 1.0f, 1000.0f));
// 更新常量缓冲区
D3D11_MAPPED_SUBRESOURCE mappedData;
HR(m_pd3dImmediateContext->Map(m_pConstantBuffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedData));
memcpy_s(mappedData.pData, sizeof(m_CBuffer), &m_CBuffer, sizeof(m_CBuffer));
m_pd3dImmediateContext->Unmap(m_pConstantBuffer.Get(), 0);

现在尝试一下效果:

利用ImGui的IO事件

通过ImGui本身提供的函数,我们能够获取到一些常用的键鼠事件:

ImVec2 pos = ImGui::GetCursorPos();         // 鼠标位置
bool active = ImGui::IsMouseDragging(ImGuiMouseButton_Left); // 鼠标左键是否在拖动
active = ImGui::IsMouseDown(ImGuiMouseButton_Right); // 鼠标右键是否处于按下状态
active = ImGui::IsKeyPressed(ImGuiKey_W); // 是否刚按下W键
active = ImGui::IsKeyReleased(ImGuiKey_S); // 是否刚松开S键
active = ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left); // 是否双击左键
// ...

还有一些事件无法通过函数获取的,我们可以使用ImGuiIO来获取:

ImGuiIO& io = ImGuiIO::GetIO();
auto& delta = io.MouseDelta; // 当前帧鼠标位移量
io.MouseWheel; // 鼠标滚轮

下面展示了利用ImGui的IO事件操作物体:

// 不允许在操作UI时操作物体
if (!ImGui::IsAnyItemActive())
{
// 鼠标左键拖动平移
if (ImGui::IsMouseDragging(ImGuiMouseButton_Left))
{
tx += io.MouseDelta.x * 0.01f;
ty -= io.MouseDelta.y * 0.01f;
}
// 鼠标右键拖动旋转
else if (ImGui::IsMouseDragging(ImGuiMouseButton_Right))
{
phi -= io.MouseDelta.y * 0.01f;
theta -= io.MouseDelta.x * 0.01f;
}
// 鼠标滚轮缩放
else if (io.MouseWheel != 0.0f)
{
scale += 0.02f * io.MouseWheel;
if (scale > 2.0f)
scale = 2.0f;
else if (scale < 0.2f)
scale = 0.2f;
}
}

现在就可以操作这个立方体了:

至此ImGui就算是入门了。由于ImGui本身是没有文档的,读者需要通过Dear ImGui Demo的窗口来寻找自己需要的控件,然后在imgui_demo.cpp中搜索对应位置的控件以查看它代码是怎么用的。这里也推荐一个网站方便检索:ImGui Manual (pthom.github.io)

后备缓冲区使用sRGB时需要解决的问题(进阶)

初学者可以跳过这一段,在遇到下面UI过亮的情况时候可以回头看。

通常情况下我们使用的渲染目标格式是DXGI_FORMAT_R8G8B8A8_UNORM的,而如果是DXGI_FORMAT_R8G8B8A8_UNORM_SRGB的话,会导致ImGui缺乏伽马校正而过亮,就像下面的图这样:

ImGui本身是对sRGB无动于衷的。根据sRGB and linear color spaces · Issue #578 · ocornut/imgui,我们可以利用ImGui提供的没有使用到的枚举值ImGuiConfigFlags_IsSRGB,然后需要对ImGui的源码做一些修改。

imgui_impl_dx11.cpp大致382行的位置,我们使用如下代码替换vertexShader,使得能够根据ImGuiConfigFlags_IsSRGB的设置与否来决定是否需要去除伽马校正:

static const char* vertexShader = nullptr;
if (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_IsSRGB)
{
vertexShader =
"cbuffer vertexBuffer : register(b0) \
{\
float4x4 ProjectionMatrix; \
};\
struct VS_INPUT\
{\
float2 pos : POSITION;\
float4 col : COLOR0;\
float2 uv : TEXCOORD0;\
};\
\
struct PS_INPUT\
{\
float4 pos : SV_POSITION;\
float4 col : COLOR0;\
float2 uv : TEXCOORD0;\
};\
\
PS_INPUT main(VS_INPUT input)\
{\
PS_INPUT output;\
output.pos = mul( ProjectionMatrix, float4(input.pos.xy, 0.f, 1.f));\
output.col = pow(input.col, 2.2f);\
output.uv = input.uv;\
return output;\
}";
}
else
{
vertexShader =
"cbuffer vertexBuffer : register(b0) \
{\
float4x4 ProjectionMatrix; \
};\
struct VS_INPUT\
{\
float2 pos : POSITION;\
float4 col : COLOR0;\
float2 uv : TEXCOORD0;\
};\
\
struct PS_INPUT\
{\
float4 pos : SV_POSITION;\
float4 col : COLOR0;\
float2 uv : TEXCOORD0;\
};\
\
PS_INPUT main(VS_INPUT input)\
{\
PS_INPUT output;\
output.pos = mul( ProjectionMatrix, float4(input.pos.xy, 0.f, 1.f));\
output.col = input.col;\
output.uv = input.uv;\
return output;\
}";
}

然后在D3DApp::InitImGui中添加ImGuiConfigFlags_IsSRGB标志:

IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // 允许键盘控制
io.ConfigFlags |= ImGuiConfigFlags_IsSRGB; // 标记当前使用的是SRGB,目前对ImGui源码有修改
io.ConfigWindowsMoveFromTitleBarOnly = true; // 仅允许标题拖动

这样显示就正常了。

DirectX11 With Windows SDK完整目录

Github项目源码

欢迎加入QQ群: 727623616 可以一起探讨DX11,以及有什么问题也可以在这里汇报。

DirectX11 With Windows SDK--06 使用ImGui的更多相关文章

  1. 粒子系统与雨的效果 (DirectX11 with Windows SDK)

    前言 最近在学粒子系统,看这之前的<<3D图形编程基础 基于DirectX 11 >>是基于Direct SDK的,而DXSDK微软已经很久没有更新过了并且我学的DX11是用W ...

  2. DirectX11 With Windows SDK--21 鼠标拾取

    前言 拾取是一项非常重要的技术,不论是电脑上用鼠标操作,还是手机的触屏操作,只要涉及到UI控件的选取则必然要用到该项技术.除此之外,一些类似魔兽争霸3.星际争霸2这样的3D即时战略游戏也需要通过拾取技 ...

  3. DirectX11 With Windows SDK--36 延迟渲染基础

    前言 随着图形硬件变得越来越通用和可编程化,采用实时3D图形渲染的应用程序已经开始探索传统渲染管线的替代方案,以避免其缺点.其中一项最流行的技术就是所谓的延迟渲染.这项技术主要是为了支持大量的动态灯光 ...

  4. DirectX11 With Windows SDK--10 摄像机类

    前言 DirectX11 With Windows SDK完整目录:http://www.cnblogs.com/X-Jun/p/9028764.html 由于考虑后续的项目需要有一个比较好的演示环境 ...

  5. DirectX11 With Windows SDK--27 计算着色器:双调排序

    前言 上一章我们用一个比较简单的例子来尝试使用计算着色器,但是在看这一章内容之前,你还需要了解下面的内容: 章节 26 计算着色器:入门 深入理解与使用缓冲区资源(结构化缓冲区/有类型缓冲区) Vis ...

  6. DirectX11 With Windows SDK--26 计算着色器:入门

    前言 现在开始迎来所谓的高级篇了,目前计划是计算着色器部分的内容视项目情况,大概会分3-5章来讲述. DirectX11 With Windows SDK完整目录 Github项目源码 欢迎加入QQ群 ...

  7. DirectX11 With Windows SDK--25 法线贴图

    前言 在很早之前的纹理映射中,纹理存放的元素是像素的颜色,通过纹理坐标映射到目标像素以获取其颜色.但是我们的法向量依然只是定义在顶点上,对于三角形面内一点的法向量,也只是通过比较简单的插值法计算出相应 ...

  8. DirectX11 With Windows SDK--24 Render-To-Texture(RTT)技术的应用

    前言 尽管在上一章的动态天空盒中用到了Render-To-Texture技术,但那是针对纹理立方体的特化实现.考虑到该技术的应用层面非常广,在这里抽出独立的一章专门来讲有关它的通用实现以及各种应用. ...

  9. DirectX11 With Windows SDK--23 立方体映射:动态天空盒的实现

    前言 上一章的静态天空盒已经可以满足绝大部分日常使用了.但对于自带反射/折射属性的物体来说,它需要依赖天空盒进行绘制,但静态天空盒并不会记录周边的物体,更不用说正在其周围运动的物体了.因此我们需要在运 ...

随机推荐

  1. el-menu菜单 -- unique-opened 子菜单唯一性失效

    总结: 点击的是  el-sub-menu . 所以  el-sub-menu 的唯一性是必须的.否则 unique-opened 属性不生效

  2. 【面试普通人VS高手系列】HashMap是怎么解决哈希冲突的?

    常用数据结构基本上是面试必问的问题,比如HashMap.LinkList.ConcurrentHashMap等. 关于HashMap,有个学员私信了我一个面试题说: "HashMap是怎么解 ...

  3. jsp第七周作业

    1.p78-p79的例4-9 <%@ page language="java" import="java.util.*" pageEncoding=&qu ...

  4. JS_Window-三种消息框:警告框、确认框、提示框、页面显示时间-计时-延时

    1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="U ...

  5. js数组操作集合

    1. join() 功能:将数组中所有元素都转化为字符串并连接在一起. 2. reverse() 功能:将数组中的元素颠倒顺序. 3. concat() 功能:数组拼接的功能 ,返回新数组,原数组不受 ...

  6. R2DBC正式孵化成功,利好Spring Webflux

    2022年4月25日,R2DBC社区宣布具有普遍可用性的1.0.0.RELEASE正式发布. R2DBC致力于为反应式编程 API操作关系型数据库带来规范支持,R2DBC不同于我们熟知的JDBC规范, ...

  7. 代码管理工具-Git基础介绍及常用技巧

    目录 Git起源 基本概念 Branch.HEAD和Commit tree Git分支 git merge 和 git rebase 的区别和抉择 与远程仓库的交互 关于一些实际开发场景的问题和解决方 ...

  8. XCTF练习题---WEB---robots

    XCTF练习题---WEB---robots flag:cyberpeace{6c4b08933075fc620d16d1157ee07a7e} 解题步骤: 1.观察题目,打开场景 2.打开实验场景, ...

  9. 斯坦福NLP课程 | 第1讲 - NLP介绍与词向量初步

    作者:韩信子@ShowMeAI,路遥@ShowMeAI,奇异果@ShowMeAI 教程地址:http://www.showmeai.tech/tutorials/36 本文地址:http://www. ...

  10. 优化 Docker 镜像大小常见方法

    平时我们构建的 Docker 镜像通常比较大,占用大量的磁盘空间,随着容器的大规模部署,同样也会浪费宝贵的带宽资源.本文将介绍几种常用的方法来优化 Docker 镜像大小,这里我们使用 Docker ...