如何访问加载至ID3D11ShaderResourceView的GIF动画单帧?解决DirectXTK加载GIF后ImGui仅显示首帧问题
实现GIF动画纹理在ImGui中的播放
你遇到的问题很典型——CreateWICTextureFromFile的设计目标是处理静态纹理,所以默认只会加载GIF的第一帧。要实现完整的GIF动画,需要手动解析GIF的帧数据、管理每帧的纹理资源,并根据帧延迟时间动态切换显示内容。以下是具体的实现步骤和代码示例:
1. 手动解析GIF的帧数据与元信息
DirectXTK的封装函数没有暴露GIF的动画帧,所以我们需要直接使用WIC(Windows Imaging Component)API来读取GIF的所有帧,同时获取每帧的延迟时间(控制动画播放速度):
#include <wincodec.h> #include <vector> #include <DirectXTK/TextureLoader.h> // 初始化WIC工厂 IWICImagingFactory* wicFactory = nullptr; CoCreateInstance(CLSID_WICImagingFactory2, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&wicFactory)); // 创建GIF解码器 IWICBitmapDecoder* decoder = nullptr; wicFactory->CreateDecoderFromFilename( L"sample.gif", nullptr, GENERIC_READ, WICDecodeMetadataCacheOnLoad, &decoder ); // 获取GIF总帧数 UINT frameCount = 0; decoder->GetFrameCount(&frameCount); // 存储每帧的ShaderResourceView和延迟时间 std::vector<ID3D11ShaderResourceView*> gifFrames; std::vector<UINT> frameDelays; // 单位:毫秒 // 遍历加载每一帧 for (UINT i = 0; i < frameCount; ++i) { IWICBitmapFrameDecode* frame = nullptr; decoder->GetFrame(i, &frame); // 获取当前帧的延迟时间(GIF元数据中存储的是1/100秒,需转成毫秒) IWICMetadataQueryReader* metadataReader = nullptr; frame->GetMetadataQueryReader(&metadataReader); PROPVARIANT propVar; PropVariantInit(&propVar); metadataReader->GetMetadataByName(L"/grctlext/Delay", &propVar); UINT delay = propVar.uiVal * 10; // 转换为毫秒 frameDelays.push_back(delay); PropVariantClear(&propVar); metadataReader->Release(); // 将WIC帧转换为D3D纹理和SRV ID3D11Resource* tempResource = nullptr; ID3D11ShaderResourceView* tempSRV = nullptr; HRESULT hr = CreateWICTextureFromFile(d3dDevice, frame, nullptr, &tempResource, &tempSRV); if (SUCCEEDED(hr)) { gifFrames.push_back(tempSRV); tempResource->Release(); // SRV会持有资源引用,可释放临时资源 } frame->Release(); } // 清理WIC资源 decoder->Release(); wicFactory->Release();
2. 在ImGui渲染循环中实现帧切换
有了所有帧的SRV和延迟时间后,我们需要在渲染循环中根据时间自动切换当前显示的帧:
// 静态变量保存当前帧索引和上一次切换时间 static UINT currentFrameIndex = 0; static double lastFrameSwitchTime = ImGui::GetTime(); // 获取当前时间,判断是否需要切换帧 double currentTime = ImGui::GetTime(); if (currentTime - lastFrameSwitchTime >= frameDelays[currentFrameIndex] / 1000.0) { // 循环切换帧(如果需要支持GIF的循环次数限制,可额外读取元数据处理) currentFrameIndex = (currentFrameIndex + 1) % frameCount; lastFrameSwitchTime = currentTime; } // 显示当前帧的ImageButton ImGui::ImageButton((void*)gifFrames[currentFrameIndex], ImVec2(width, height));
3. 资源清理
程序退出时,记得释放所有帧的SRV资源:
for (auto srv : gifFrames) { if (srv) srv->Release(); } gifFrames.clear(); frameDelays.clear();
额外注意事项
- GIF循环次数:如果需要遵循GIF的循环次数限制,可以读取元数据
/appext/loopcount,值为0表示无限循环,否则循环指定次数后停止。 - 透明通道:WIC和DirectXTK会自动处理GIF的透明通道,无需额外配置,但要确保D3D设备支持对应的纹理格式(如
DXGI_FORMAT_R8G8B8A8_UNORM)。 - 性能优化:如果GIF帧数较多,可以考虑按需加载帧,或者使用纹理数组来减少资源开销,但对于大多数场景,直接存储每帧的SRV已经足够。
内容的提问来源于stack exchange,提问作者mohammad-ghasemi-2549
相关产品推荐
相关产品推荐

