Vulkanレイトレーシングで気軽に遊ぶ

公開:2020/10/02
更新:2020/10/03
10 min読了の目安(約9100字TECH技術記事

0. はじめに

2020年3月、Vulkanのレイトレーシング拡張機能がリリースされました。この記事ではサンプルプロジェクトをアップデートしながら、気軽にレイトレ拡張を触っていきます。

目標

三角形が表示されるサンプルを弄り、シェーディングされたモデルが表示されるようにします。

環境

動作環境、開発環境は以下を想定しています。

  • Windows10 64bit
  • Vulkan SDK 1.2.148
  • NVIDIA GeForce 1070Ti
  • Vulkan Beta Driver Windows 452.28
  • Visual Studio 2019

Vulkan SDKのインストール

まずはVulkan SDKをインストールします。レイトレ拡張を使うにはVulkan1.2.135以降を選択してください。
SDKダウンロードページ

Vulkan Beta Driverのインストール

2020年9月現在、Vulkanレイトレ拡張を動かすためにはGeneral Release Driverではなく、Developer Beta Driverが必要です。
ドライバーダウンロードページ

勘違いしやすいところですが、Vulkanレイトレ拡張はRTXシリーズ以外でも動作します。対応GPUは上記ページで確認してください。

Vulkanサンプルを動かす

SaschaWillems氏の素晴らしいサンプルリポジトリをお借りすることにします。
github.com/SaschaWillems/Vulkan

git clone --recursive https://github.com/SaschaWillems/Vulkan.git
cd Vulkan

clone後、アセットのダウンロードを行う必要があります。pythonプログラムが同封されているのでそれを実行すればOKです。

python download_assets.py

cmakeを実行すればbuild/vulkanExamples.slnができるはずです。

cmake . -B build

vulkanExamples.slnをVSで開き、raytracingbasicをスタートアップに設定します。

実行してみると三角形が表示されました。

これで準備が整いました。この記事は気軽に動かすことを目標としているので、このソリューションをそのまま使ってしまいます。また、Vulkanの初期化周りは用意されているクラスやヘルパー関数などに全て任せ、レイトレに関係する部分だけを触っていきます。

以降のプログラムの大部分は上記リポジトリ内のコードを一部変更したものです。

The MIT License (MIT)
Copyright (c) 2016 Sascha Willems
https://github.com/SaschaWillems/Vulkan/blob/master/LICENSE.md

1. シェーダを変更して色を変える

早速シェーダを変更して色が変わることを確認してみます。

この章で得る知識

  1. SPIR-V
  2. GLSLのコンパイル方法
  3. 3種類のレイトレ用シェーダ
  4. レイペイロード

シェーダをコンパイルするバッチファイルを作る

VulkanではGLSLのプログラムをSPIR-V形式にコンパイルする必要があります。コンパイルにはいくつかの方法がありますが、ここではSDKに含まれているglslc.exeを使用します。

まずはシェーダをコンパイルするためのバッチファイルを作成しておきましょう。GLSLシェーダファイルはVulkan\data\shaders\glsl\raytracingbasicの中にあるので、同じディレクトリにcompile.batを用意し、以下のように記述します。レイトレ用のコードをコンパイルするため、--target-env=vulkan1.2を指定しています。

%VULKAN_SDK%/Bin32/glslc.exe closesthit.rchit -o closesthit.rchit.spv --target-env=vulkan1.2
%VULKAN_SDK%/Bin32/glslc.exe miss.rmiss -o miss.rmiss.spv --target-env=vulkan1.2
%VULKAN_SDK%/Bin32/glslc.exe raygen.rgen -o raygen.rgen.spv --target-env=vulkan1.2
pause

背景とポリゴンの色を変える

レイトレ用のシェーダは以下の3つを使用します。

  • RayGenシェーダ
    レイを生成し、トレースを開始するシェーダ
  • ClosestHitシェーダ
    レイが当たった箇所のうち、最も近い箇所で実行されるシェーダ
  • Missシェーダ
    レイがどこにもヒットしなかったときに実行されるシェーダ

その他にもAnyHitシェーダ、Intersectionシェーダがありますが、今回は扱いません。

まずは背景色を変えるため、Missシェーダmiss.rmissの中のhitValueの値を変えます。この変数はレイペイロード rayPayloadInEXT と呼ばれる変数で、レイに沿って情報を運ぶ役割をします。通常は光やマテリアルの情報を格納することが多いでしょう。
ちなみにペイロードは通信において運びたいデータのことを指す用語ですので、レイの経路が通信路と対応し、レイペイロードが通信データと対応するイメージなのかなと思います。

void main()
{
    // hitValue = vec3(0.0, 0.0, 0.2);
    hitValue = vec3(0.0, 0.5, 0.2);
}

同様にポリゴン色を変えるため、ClosestHitシェーダclosesthit.rchitも変更します。

void main()
{
    const vec3 barycentricCoords = vec3(1.0f - attribs.x - attribs.y, attribs.x, attribs.y);
    // hitValue = barycentricCoords;
    hitValue = vec3(1.0);
}

さきほど作成したcompile.batでコンパイルを行い、プログラムを実行してみると色が変わったことが確認できると思います。

2. モデルを読み込んでレイトレする

次にglTF形式のモデルを読み込み、レイトレースできるようにセットアップしていきます。

この章で得る知識

  1. Acceleration Structure (AS)
  2. Bottom-Level AS (BLAS) の構築

Acceleration Structureについて

レイトレーシングの交差判定は頂点バッファではなく、Acceleration Structure(AS)というデータを使用します。ASは以下の2つに分かれています。

  • Bottom-Level AS (BLAS)
    実際に頂点データなどを保持する構造
  • Top-Level AS (TLAS)
    BLASへの参照と、それに対する変換行列などを保持する構造


出典:NVIDIA Vulkan Ray Tracing Tutorial

ASは具体的な実装に依存しない形で公開されていますが、基本的にはBVH(Bounding Volume Hierarchy)をイメージすればいいようです。BVHはポリゴン集合を囲むAABB(Axis-Aligned Bounding Box)の木構造で、葉ノードに1枚のポリゴンを持っています。この構造を使うとポリゴン数 nn に対して、レイとの交差判定回数が大体 logn\log n に抑えられるため、高速に処理が可能となります。そのため「"Acceleration" Structure」と呼んでいるようです。

glTFモデルでBLASを構築する

モデルをレイトレに使用するためにはBLASの構築を行う必要があります。サンプルプログラムの中のcreateBottomLevelAccelerationStructure()関数がそれを行っているのですが、量が多いので内容を大雑把にまとめます。

  1. 頂点配列とインデックス配列を作成
  2. 頂点バッファとインデックスバッファを作成
  3. BLASハンドルを作成
  4. BLAS用のメモリを確保してハンドルと結び付ける
  5. ビルドコマンドを実行

glTFをロードする

まずはglTFファイルをロードします。有難いことにサンプルの中にglTFローダーも含まれているので、それを使用します。はじめにglTF関連のヘッダーをincludeし、Modelのメンバ変数を追加します。

#include "VulkanglTFModel.h"
class VulkanExample
{
    vkglTF::Model model;
}

次にcreateBottomLevelAccelerationStructure()の中の、ハードコード部分を書き換えます。ここではアセットに含まれているティーポットのモデルを読み込んでみましょう。

//// Setup vertices for a single triangle
//struct Vertex
//{
//    float pos[3];
//};
//std::vector<Vertex> vertices = {
//    { {  1.0f,  1.0f, 0.0f } },
//    { { -1.0f,  1.0f, 0.0f } },
//    { {  0.0f, -1.0f, 0.0f } }
//};

//// Setup indices
//std::vector<uint32_t> indices = { 0, 1, 2 };
//indexCount = static_cast<uint32_t>(indices.size());

vkglTF::memoryPropertyFlags = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
const uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY;
model.loadFromFile(getAssetPath() + "models/teapot.gltf", vulkanDevice, queue, glTFLoadingFlags);

const uint32_t numTriangles = static_cast<uint32_t>(model.indices.count) / 3;

このとき、ローダー側では頂点バッファとインデックスバッファの確保まで行ってくれており、Modelクラス内部にハンドルを持っています。

class vkglTF::Model
{
    struct Vertices {
        int count;
        VkBuffer buffer; // 頂点バッファ
        VkDeviceMemory memory;
    } vertices;

    struct Indices {
        int count;
        VkBuffer buffer; // インデックスバッファ
        VkDeviceMemory memory;
    } indices;
}

そのためバッファを作成していたコードは不要になります。同様に、バッファ用のメンバ変数も削除します。

class VulkanExample
{
    // vks::Buffer vertexBuffer;
    // vks::Buffer indexBuffer;
    // uint32_t indexCount;
    
    ~VulkanExample()
    {
        //vertexBuffer.destroy();
        //indexBuffer.destroy();
    }
    
    void createBottomLevelAccelerationStructure()
    {
	// モデルのロードを行ったコード
	// ...
	
        //// Create buffers
	//// For the sake of simplicity we won't stage the vertex data to the GPU memory
	//// Vertex buffer
	//VK_CHECK_RESULT(vulkanDevice->createBuffer(
	//	VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
	//	VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
	//	&vertexBuffer,
	//	vertices.size() * sizeof(Vertex),
	//	vertices.data()));
	//// Index buffer
	//VK_CHECK_RESULT(vulkanDevice->createBuffer(
	//	VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
	//	VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
	//	&indexBuffer,
	//	indices.size() * sizeof(uint32_t),
	//	indices.data()));
	
	// ...
    }
}

BLASビルドを行う

これでモデルのロードとバッファの作成が完了したので、その他ビルドに必要な情報を修正していきます。構造体がいくつか出てきますが、あまり詳細は気にせずに行きます。

まずはバッファのデバイスアドレスを取得している部分をModelクラス内部から取得するように変更します。

//vertexBufferDeviceAddress.deviceAddress = getBufferDeviceAddress(vertexBuffer.buffer);
//indexBufferDeviceAddress.deviceAddress = getBufferDeviceAddress(indexBuffer.buffer);
vertexBufferDeviceAddress.deviceAddress = getBufferDeviceAddress(model.vertices.buffer);
indexBufferDeviceAddress.deviceAddress = getBufferDeviceAddress(model.indices.buffer);

VkAccelerationStructureCreateGeometryTypeInfoKHRを修正します。

  • maxPrimitiveCount
    三角形の数を指定する
  • maxVertexCount
    頂点数を指定する
//accelerationCreateGeometryInfo.maxPrimitiveCount = 1;
accelerationCreateGeometryInfo.maxPrimitiveCount = numTriangles;
//accelerationCreateGeometryInfo.maxVertexCount = static_cast<uint32_t>(vertices.size());
accelerationCreateGeometryInfo.maxVertexCount = static_cast<uint32_t>(model.vertices.count);

VkAccelerationStructureGeometryKHRを修正します。

  • geometry.triangles.vertexStride
    頂点データ間のストライドを指定する
//accelerationStructureGeometry.geometry.triangles.vertexStride = sizeof(Vertex);
accelerationStructureGeometry.geometry.triangles.vertexStride = sizeof(vkglTF::Vertex);

VkAccelerationStructureBuildOffsetInfoKHRを修正します。

  • primitiveCount
    三角形の数を指定する
//accelerationBuildOffsetInfo.primitiveCount = 1;
accelerationBuildOffsetInfo.primitiveCount = numTriangles;

BLASのビルドコマンドを実行する部分の変更はありません。これでプログラムを実行してみると以下のような画が出ました。

モデルが大き過ぎますが、マウスホイールでのカメラ移動も実装されているので、良い感じに調整するとちゃんとティーポットが描画されていることが確認できます。

以上でglTFモデルのレイトレが可能になりました。記事が長くなってきたのでシェーディングに関しては次の記事に書こうと思います。ここまで読んでいただきありがとうございました。

参考文献

「Vulkan Tutorial」
https://vulkan-tutorial.com/

「NVIDIA Vulkan Ray Tracing Tutorial」
https://nvpro-samples.github.io/vk_raytracing_tutorial_KHR/

「Vulkan Programming Vol.1」すらりんラボ
https://slash-labo.booth.pm/items/1286100

「3DグラフィクスAPI Vulkanを出来るだけやさしく解説する本」FADIS PRESS
https://booth.pm/ja/items/1562222