一、DirectX 集成:Windows 平台图形编程的基石

DirectX,说白了就是 Windows 上做游戏和图形应用绕不开的那套 API。我最早接触 DirectX 还是做一个小型 3D 渲染器的时候,那时候被 COM 接口折腾得够呛。但话说回来,一旦你掌握了在 CMake 里正确集成 DirectX 的方法,后面的事情就顺多了。

这一讲,我们就来聊聊怎么在 CMake 项目中把 DirectX 请进来。嗯,这里主要针对 DirectX 11 和 DirectX 12,因为这两个版本是目前最常用的。

1.1 DirectX 的两种集成方式

集成 DirectX 其实有两条路可以走:

  • 使用 Windows SDK 自带的 DirectX —— 这是最正统的方式。Windows SDK 里已经包含了 DirectX 的头文件和库文件,你不需要额外下载任何东西。
  • 使用 DirectX 官方 NuGet 包 —— 微软后来把 DirectX 的头文件也放到了 NuGet 上,方便版本管理。我个人更推荐这种方式,尤其是团队协作时,可以锁定版本。

不过,不管走哪条路,CMake 里配置的核心思路是一样的:找到头文件路径,链接对应的 .lib 文件。

1.2 方式一:使用 Windows SDK

Windows SDK 安装后,DirectX 的头文件通常在 C:\Program Files (x86)\Windows Kits\10\Include\10.0.xxxxx.0\um 目录下。库文件则在 Lib\10.0.xxxxx.0\um\x64x86 下。

在 CMake 里,我们可以通过 find_pathfind_library 来定位它们。但说实话,手动写这些查找脚本有点啰嗦。我一般直接用 target_link_libraries 配合 Windows 平台的内置宏,省事不少。

来看一个最简示例:

cmake_minimum_required(VERSION 3.16)
project(DirectXDemo)

add_executable(dx_app main.cpp)

# 链接 DirectX 11 的库
target_link_libraries(dx_app
    d3d11
    dxgi
    dxguid
)

# 如果是 DirectX 12,则链接:
# target_link_libraries(dx_app d3d12 dxgi dxguid)

嗯,你没看错,就这么简单。CMake 在 Windows 上能自动找到 Windows SDK 里的这些库名。为什么?因为这些库名是 Windows SDK 的标准组成部分,CMake 的默认搜索路径已经覆盖了它们。

小提示: 如果你需要链接 DirectX 的调试库(比如 d3d11d.lib),可以在 Debug 配置下用 $<CONFIG:Debug> 生成器表达式来区分。我习惯这样写:target_link_libraries(dx_app $<$<CONFIG:Debug>:d3d11d> $<$<NOT:$<CONFIG:Debug>>:d3d11>)

1.3 方式二:使用 NuGet 包(推荐)

如果你希望 DirectX 的版本和项目绑定,不受系统 SDK 版本影响,那用 NuGet 包是更好的选择。微软官方提供了 Microsoft.Direct3D.D3D11Microsoft.Direct3D.D3D12 等包。

在 CMake 中集成 NuGet 包,我推荐用 nuget 命令行配合 execute_process,或者直接用 FetchContent 下载头文件。不过,更现代的做法是用 vcpkg 来管理。

这里我演示用 vcpkg 的方式:

# 安装 DirectX 头文件
# vcpkg install directx-headers

cmake_minimum_required(VERSION 3.16)
project(DirectXDemo)

# 启用 vcpkg 工具链
# cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE=[vcpkg-root]/scripts/buildsystems/vcpkg.cmake

add_executable(dx_app main.cpp)

# 找到 DirectX 头文件
find_path(DIRECTX_INCLUDE_DIR
    NAMES d3d11.h
    PATHS ${CMAKE_SOURCE_DIR}/vcpkg_installed/x64-windows/include
)

target_include_directories(dx_app PRIVATE ${DIRECTX_INCLUDE_DIR})
target_link_libraries(dx_app d3d11 dxgi dxguid)

我个人更倾向 vcpkg 的方式,因为版本锁定清晰,团队里每个人拉下来都能编译过,不会出现「我机器上能跑,你机器上不行」的尴尬。

1.4 核心知识结构图

下面这张图总结了 DirectX 集成的核心逻辑,我把它画成了流程图,方便你理解整个决策过程:

DirectX 集成决策流程图 开始集成 DirectX 使用 Windows SDK? 还是 NuGet/vcpkg? Windows SDK 方式 target_link_libraries d3d11 / d3d12 NuGet / vcpkg 方式 find_path + find_library target_include_directories 编译通过,集成完成

1.5 实战:完整的 CMakeLists.txt

下面我给出一个完整的示例,它同时支持 DirectX 11 和 DirectX 12,并且考虑了 Debug/Release 配置的库区分:

cmake_minimum_required(VERSION 3.16)
project(DirectXIntegration VERSION 1.0.0 LANGUAGES CXX)

# 设置 C++ 标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 可执行目标
add_executable(${PROJECT_NAME}
    src/main.cpp
    src/d3d11_renderer.cpp
    src/d3d11_renderer.h
)

# 选择 DirectX 版本:11 或 12
option(USE_D3D12 "Use DirectX 12 instead of 11" OFF)

if(USE_D3D12)
    target_compile_definitions(${PROJECT_NAME} PRIVATE USE_D3D12=1)
    target_link_libraries(${PROJECT_NAME}
        d3d12
        dxgi
        dxguid
        $<$<CONFIG:Debug>:d3d12d>
    )
else()
    target_link_libraries(${PROJECT_NAME}
        d3d11
        dxgi
        dxguid
        $<$<CONFIG:Debug>:d3d11d>
    )
endif()

# 额外链接 Windows 常用库
target_link_libraries(${PROJECT_NAME}
    kernel32
    user32
    gdi32
    winspool
    comdlg32
    advapi32
    shell32
    ole32
    oleaut32
    uuid
)
关键点: 注意 $<$<CONFIG:Debug>:d3d11d> 这种写法。它表示:如果当前是 Debug 配置,就链接 d3d11d.lib(调试版),否则不链接。这是 CMake 生成器表达式的典型用法,我建议你熟练掌握它。

1.6 避坑指南

我在集成 DirectX 的过程中踩过不少坑,这里挑几个典型的说说:

  • 头文件找不到? 我曾经因为 Windows SDK 版本没选对,导致 d3d11.h 死活找不到。后来发现是 CMake 默认用了旧版 SDK。解决办法:在 CMake 里显式设置 CMAKE_SYSTEM_VERSION 为你的 SDK 版本号。
  • 链接错误 LNK2019? 这通常是因为你忘了链接 dxguid.lib。DirectX 的许多 GUID 定义都在这个库里,少了它就会报一堆未解析的外部符号。
  • 32位和64位混用? 确保你的库路径和生成目标架构一致。我见过有人用 x64 的库链接到 Win32 项目上,结果链接器报错说格式不对。
注意: 如果你在 CMake 中同时使用了 find_packagetarget_link_libraries 两种方式来找 DirectX,可能会造成冲突。建议只选一种方式,保持项目配置的一致性。

1.7 小结

DirectX 集成其实不复杂,核心就是两件事:找到头文件,链接库文件。Windows SDK 方式简单直接,适合快速上手;NuGet/vcpkg 方式更规范,适合团队协作。我个人在正式项目中都用 vcpkg,因为版本管理太省心了。

你想想看,一旦把 DirectX 集成搞定了,后面写渲染代码、处理窗口消息、创建交换链,这些就都是水到渠成的事了。嗯,下一讲我们聊聊 DirectX 的初始化流程,到时候会用到今天配置好的环境。


公众号:蓝海资料掘金营,微信 deep3321