前言

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. 2021.07.18 P2290 树的计数(prufer序列、组合数学)

    2021.07.18 P2290 树的计数(prufer序列.组合数学) [P2290 HNOI2004]树的计数 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 重点: 1.pru ...

  2. Dubbo 学习笔记

    分布式基础理论 1. 什么是分布式系统? 分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个系统 2. 应用架构演变 单一应用架构 当网站流量很小时,只需一个应用,将所有功能都部署在一起 ...

  3. Ubuntu 20.0.4 安装 NVIDIA N卡 驱动 画面撕裂 解决方法

    电脑 联想 Y7000 系统 Ubuntu 20.0.4 显卡 NVIDIA 1050TI 以下操作需要管理员权限 编辑文件,如果没有新建一个 /lib/modprobe.d/nvidia-graph ...

  4. 数据库基础知识详解五:MySQL中的索引和其两种引擎、主从复制以及关系型/非关系型数据库

    1.MySQL中的索引 在MySQL,索引是由B+树实现的,B+是一种与B树十分类似的数据结构. 形如下面这种: 其结构特点: (1)有n课子树的结点中含有n个关键码. (2)非根节点子节点数: ce ...

  5. MySQL基础之写表(创建表)

    我的博客 工具 市面上的SQL可视化工具不少,我一般常用的主要就是这两个. 当然,IDEA也是集成了数据库可视化功能的.除了这些,还有DBeaver.SQLyog等等. 我比较喜欢DataGrip,我 ...

  6. go学习第一课--语法基础

    一.hello world 新建文件helloworld.go package main import "fmt" func main() { fmt.Println(      ...

  7. 解析数仓OLAP函数:ROLLUP、CUBE、GROUPING SETS

    摘要:GaussDB(DWS) ROLLUP,CUBE,GROUPING SETS等OLAP函数的原理解析. 本文分享自华为云社区<GaussDB(DWS) OLAP函数浅析>,作者: D ...

  8. 干货 | 亿级Web系统负载均衡几种实现方式

    一个执着于技术的公众号 负载均衡(Load Balance)是集群技术(Cluster)的一种应用技术.负载均衡可以将工作任务分摊到多个处理单元,从而提高并发处理能力.目前最常见的负载均衡应用是Web ...

  9. K8S面试应知必回

    目录 面试不要不懂装懂,不会就是不会,不可能每个人都接触过所有的知识! 1. 基础问题 1.1 Service是怎么关联Pod的?(课程Service章节) 1.2 HPA V1 V2的区别 1.3 ...

  10. C# 给Word中的字符添加强调符号(着重号)

    在Word中添加着重号,即强调符号,可以在选中字符后,鼠标右键点击,选择"字体",在窗口中可直接选择"着重号"添加到文字,用以对重要文字内容起加强提醒的目的,如 ...