You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何访问加载至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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 09:32:34