🌋
Vulkanでレンダリング画像を保存する
はじめに
DeviceLocal
のVkImage
をストレージに保存する機能を実装したので、内容を紹介します。
後から気が付いたのですが、SaschaWillems/Vulkanにも同じ目的のサンプルがありました。手法は少し違いますが、こちらも参考になると思います。
大まかな流れはこのようになります。
- 一時VkBufferをHost側に作成する
- VkBufferをHostメモリにMapする
- VkImageをVkBufferにコピーする
- 画像フォーマットを変換する
- 画像を保存する
ちなみに、先ほど紹介したサンプルの方では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);
おわり
以上です。雰囲気は伝わるかなと思いますが、一応動くコードへのリンクも載せておきます。記事用のコードじゃないので、かなり読みづらいと思いますが...
Discussion