🌋

Vulkanでレンダリング画像を保存する

2021/05/02に公開

はじめに

DeviceLocalVkImageをストレージに保存する機能を実装したので、内容を紹介します。
後から気が付いたのですが、SaschaWillems/Vulkanにも同じ目的のサンプルがありました。手法は少し違いますが、こちらも参考になると思います。

大まかな流れはこのようになります。

  1. 一時VkBufferをHost側に作成する
  2. VkBufferをHostメモリにMapする
  3. VkImageをVkBufferにコピーする
  4. 画像フォーマットを変換する
  5. 画像を保存する

ちなみに、先ほど紹介したサンプルの方ではBufferではなくImageにコピーしていました。個人的にはBufferの方が少しだけ楽に感じます。

コードは正確に書くと長大になってしまうので、雰囲気が分かる程度にしか記述しません。参考程度にご覧ください。

1. 一時VkBufferをHost側に作成する

// TransferDst用のバッファを作成する
vk::DeviceSize size = image.width * image.height * 4;
Buffer buffer = createBuffer(size, vk::BufferUsageFlagBits::eTransferDst);

// HostVisibleでメモリ確保する
buffer.allocate(vk::MemoryPropertyFlagBits::eHostVisible);

2. VkBufferをHostメモリにMapする

// メモリをマップしてポインターを取得しておく
uint8_t* pixels = reinterpret_cast<uint8_t*>(buffer.map());

3. VkImageをVkBufferにコピーする

vk::CommandBuffer cmdBuf = allocateCommandBuffer();
cmdBuf.begin();

// 1. ImageLayoutを転送元に設定する
image.transitionImageLayout(cmdBuf, vk::ImageLayout::eTransferSrcOptimal);

// 2. ImageをBufferにコピーする
vk::BufferImageCopy copyInfo;
cmdBuf.copyImageToBuffer(image, vk::ImageLayout::eTransferSrcOptimal, 
                         buffer, copyInfo);

// 3. ImageLayoutを元に戻す
image.transitionImageLayout(cmdBuf, vk::ImageLayout::eGeneral);

cmdBuf.end();
queue.submit();

4. 画像フォーマットを変換する

ここで1ピクセルずつ処理しているのが微妙ですね。もっといい方法に変更したいです。

// BGRA to RGB
std::vector<uint8_t> output(width * height * 3);
for (int h = 0; h < height; h++) {
    for (int w = 0; w < width; w++) {
        int index = h * width + w;
        output[index * 3 + 0] = pixels[index * 4 + 2];
        output[index * 3 + 1] = pixels[index * 4 + 1];
        output[index * 3 + 2] = pixels[index * 4 + 0];
    }
}

5. 画像を保存する

stb_imageを使いましたが、もちろんなんでも大丈夫です。

#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <stb_image_write.h>

stbi_write_png("img.png", width, height, 3, output.data(), width * 3);

おわり

以上です。雰囲気は伝わるかなと思いますが、一応動くコードへのリンクも載せておきます。記事用のコードじゃないので、かなり読みづらいと思いますが...

https://github.com/nishidate-yuki/Reactive/blob/master/Reactive/Source/RenderingSystem/RenderingSystem.cpp

Discussion