Skip to content

(日本語版) Alexander OvervoordeのVulkanチュートリアルのJava版

Notifications You must be signed in to change notification settings

Genbuchan/Vulkan-Tutorial-Java-Japanese

 
 

Repository files navigation

JavaのVulkanチュートリアル (日本語版)

Alexander Overvoordeによる素晴らしいチュートリアルをJavaに移植したものです。オリジナルのコードはこちら

チュートリアル画像3


はじめに

これらのチュートリアルは、C++のチュートリアルに沿って簡単に進められるように書かれています。

なお、JavaとLWJGLのスタイルに合わせるために、いくつかの変更が行われています。当リポジトリはオリジナルと同じ構造になっています。

各チャプターには、それぞれ別々のJavaのコードが用意されています。しかし、多くのチャプターで共通するクラスがいくつかあります。

  • AlignmentUtils: 均一なバッファオブジェクトのアライメントを扱うためのユーティリティクラス。
  • Frame: 実行中のフレームに必要なVulkanハンドル (画像が利用可能なセマフォレンダー完了セマフォフェンス)。
  • ModelLoader: 3Dモデルを読み込むためのユーティリティクラス。Assimpを利用しています。
  • ShaderSPIRVUtils: GLSLシェーダをランタイムでSPIRVのバイナリにコンパイルするためのユーティリティクラス。

数値計算には、グラフィック数学用のJavaのライブラリであるJOMLを使用します。これはGLMにかなりよく似ています。

最後に、各章にはそれぞれ.diffファイルが用意されており、章ごとの変更点をすぐ確認できます。

なお、本チュートリアルのJavaのコードはC/C++よりも冗長であり、その分ソースファイルが大きくなることをご了承ください。

LWJGL

今回は、LWJGLという極めて低レベルなGLFW、Vulkan、OpenGL、その他CライブラリのJavaバインディングを使用します。

もしあなたがLWJGLを知らないのであれば、このチュートリアルに登場する概念やパターンを理解するのが難しいかもしれません。

ここでは、コードを正しく理解するために必要な、最も重要な概念を簡単に説明します。

ネイティブハンドラ

Vulkanには、VkImageVkBufferVkCommandPoolなど、適切に名付けられた独自のハンドルがあります。

これらは裏では符号なしの整数値であり、Javaにはその型定義がありません。

そのため、これらのオブジェクトの全ての型としてlong型を使用する必要があります。

これが、long型の変数がたくさん登場する理由です。

ポインタと参照

構造体や関数の中には、他の変数への参照やポインタをパラメータとして受け取るものがあります。例えば、複数の値を出力するものが該当します。

この関数をC言語で考えてみましょう。

int width;
int height;

glfwGetWindowSize(window, &width, &height);

// widthとheightは、ウィンドウの縦横の大きさです。

2つのint型のポインタを渡すと、関数はそのポインタが指すメモリを書き込みます。簡単で、しかも高速ですね。

では、Javaではどうなるでしょうか。そもそもポインタという概念がありませんよね。参照のコピーを渡して、関数の中でオブジェクトの内容を変更することはできますが、プリミティブ型ではそれができません。

方法は2つあります。まず、int型の配列を使用するか、JavaのNIOバッファを使用するかです。

LWJGLのバッファは、基本的にはウィンドウの配列で、内部に位置と制限があります。

後述しますが、これらのバッファを使用する際、ヒープから割り当てることができます。

上記のglfwGetWindowSize関数をNIOバッファで扱う場合は、以下のようになります。

IntBuffer width = BufferUtils.createIntBuffer(1);
IntBuffer height = BufferUtils.createIntBuffer(1);

glfwGetWindowSize(window, width, height);

// Print the values 
System.out.println("width = " + width.get(0));
System.out.println("height = " + height.get(0));

やりました!プリミティブな値のポインタを渡すことができましたね。…でも、たった2つの整数のために、2つも新しいオブジェクトを動的に割り当ててしまっています。

もしこの2つのオブジェクトが、短時間しか必要ないものだったらどうしましょう?ガベージコレクタが、これらの使い捨ての変数を掃除してくれるのを待たないといけませんね。

幸いなことに、LWJGLは独自のメモリ管理システムでこの問題を解決しています。詳しくはこちらで知ることができます。

スタックの割り当て

C/C++では、スタック上に簡単にオブジェクトを割り当てることができます。

VkApplicationInfo appInfo = {};
// ...

しかし、Javaではこれができません。

が、これまた幸運なことに、LWJGLではスタック上に変数を割り当てることができるようになっています。そのためには、MemoryStackのインスタンスが必要です。

スタックフレームは、関数の最初にプッシュ(スタックの末尾に追加)され、最後にポップ(取り出し)します。そのため、途中で何が起こっても構いません。try-with-resources構文を使い、この動作を真似てみましょう。

try(MemoryStack stack = stackPush()) {

  // ...
  
  
} // この行では、スタックがポップされ、このスタックフレーム内の全ての変数が解放されます。

いいですね!Javaでスタックの割り当てができるようになりました。どんなものか見てみましょう。

try(MemoryStack stack = stackPush()) {

  IntBuffer width = stack.mallocInt(1); // int型を1つ割り当て(未初期化)
  IntBuffer height = stack.ints(0); // 0で初期化

  glfwGetWindowSize(window, width, height);

  // 値を出力
  System.out.println("width = " + width.get(0));
  System.out.println("height = " + height.get(0));
}

さあ、MemoryStackを使った実際のVulkanの例を見てみましょう。

private void createInstance() {

    try(MemoryStack stack = stackPush()) {

        // 構造体を0で初期化するにはcallocを使います。そうしないと、ランダムな値が原因でプログラムがクラッシュすることがあります。

        VkApplicationInfo appInfo = VkApplicationInfo.callocStack(stack);

        appInfo.sType(VK_STRUCTURE_TYPE_APPLICATION_INFO);
        appInfo.pApplicationName(stack.UTF8Safe("Hello Triangle"));
        appInfo.applicationVersion(VK_MAKE_VERSION(1, 0, 0));
        appInfo.pEngineName(stack.UTF8Safe("No Engine"));
        appInfo.engineVersion(VK_MAKE_VERSION(1, 0, 0));
        appInfo.apiVersion(VK_API_VERSION_1_0);

        VkInstanceCreateInfo createInfo = VkInstanceCreateInfo.callocStack(stack);

        createInfo.sType(VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO);
        createInfo.pApplicationInfo(appInfo);
        // enabledExtensionCountは、ppEnabledExtensionNamesを呼び出した際に暗黙的に設定されます。
        createInfo.ppEnabledExtensionNames(glfwGetRequiredInstanceExtensions());
        // enabledLayerCountでも同様です。
        createInfo.ppEnabledLayerNames(null);

        // 作成したインスタンスのポインタを取得する必要があります。
        PointerBuffer instancePtr = stack.mallocPointer(1);

        if(vkCreateInstance(createInfo, null, instancePtr) != VK_SUCCESS) {
            throw new RuntimeException("Failed to create instance");
        }

        instance = new VkInstance(instancePtr.get(0), createInfo);
    }
}

三角形の描画

準備

基本のコード

Java版チュートリアル (日本語)

C++版チュートリアル (英語)

c++元のコード

javaJavaのコード

インスタンス

チュートリアル

c++元のコード

javaJavaの

差分差分

検証レイヤー

チュートリアル

c++元のコード

javaJavaのコード

差分差分

物理デバイスとキューファミリー

チュートリアル

c++元のコード

javaJavaのコード

差分差分

論理デバイスとキュー

チュートリアル

c++元のコード

javaJavaのコード

差分差分

プレゼンテーション

ウィンドウサーフェス

チュートリアル

c++元のコード

javaJavaのコード

差分差分

スワップチェーン

チュートリアル

c++元のコード

javaJavaのコード

差分差分

画像ビュー

チュートリアル

c++元のコード

javaJavaのコード

差分差分

グラフィックパイプラインの基礎

はじめに

チュートリアル

c++元のコード

javaJavaのコード

差分差分

シェーダーモジュール

シェーダーはshadercライブラリを用いてSPIRVにコンパイルされます。GLSLファイルはresources/shadersフォルダにあります。

チュートリアル

c++元のコード

javaJavaのコード

差分差分

固定機能

チュートリアル

c++元のコード

javaJavaのコード

差分差分

レンダーパス

チュートリアル

c++元のコード

javaJavaのコード

差分差分

まとめ

チュートリアル

c++元のコード

javaJavaのコード

差分差分

描画

フレームバッファ

チュートリアル

c++元のコード

javaJavaのコード

差分差分

命令バッファ

チュートリアル

c++元のコード

javaJavaのコード

差分差分

レンダリングとプレゼンテーション

チュートリアル

c++元のコード

javaJavaのコード

差分差分

スワップチェーンの再作成

チュートリアル

c++元のコード

javaJavaのコード

差分差分

頂点バッファ

頂点入力の解説

(検証レイヤーのエラーが発生しますが、次の章で修正します。)

チュートリアル

c++元のコード

javaJavaのコード

差分差分

頂点バッファの作成

チュートリアル

c++元のコード

javaJavaのコード

差分差分

ステージングバッファ

チュートリアル

c++元のコード

javaJavaのコード

差分差分

独立した転送キューのバージョン

javaJavaのコード

差分差分

インデックスバッファ

チュートリアル

c++元のコード

javaJavaのコード

差分差分

ユニフォームバッファ

ユニフォームバッファオブジェクト

記述子のレイアウトとバッファ

チュートリアル

c++元のコード

javaJavaのコード

差分差分

記述子のプールとセット

チュートリアル

c++元のコード

javaJavaのコード

差分差分

テクスチャマッピング

画像

チュートリアル

c++元のコード

javaJavaのコード

差分差分

画像ビューとサンプラー

チュートリアル

c++元のコード

javaJavaのコード

差分差分

結合した画像のサンプラー

チュートリアル

c++元のコード

javaJavaのコード

差分差分

深度バッファリング

チュートリアル

c++元のコード

javaJavaのコード

差分差分

モデルの読み込み

モデルの読み込みにはAssimpを使用します。これは、異なるフォーマットの3Dモデルを読み込むライブラリであり、LWJGLのバインディングの一部として含まれています。

今回は、Assimpを用いたモデルの読み込みに関する処理をModelLoaderクラスにまとめています。

チュートリアル

c++元のコード

javaJavaのコード

差分差分

ミップマップの生成

チュートリアル

c++元のコード

javaJavaのコード

差分差分

マルチサンプリング

チュートリアル

c++元のコード

javaJavaのコード

差分差分


アイコンはIcon Mafiaが制作しました。

日本語版の免責事項

当ドキュメントは、GenbuchanがJavaでVulkanを学習するために、独自で日本語化したものです。

そのため、原文と日本語版の文脈に齟齬がある可能性があります。

もし、ドキュメントの問題の指摘や改善に協力してくださる方がいらっしゃいましたら、IssueまたはPull Requestをお願いします。

翻訳の補助にはDeepL Translatorを使用しました。

About

(日本語版) Alexander OvervoordeのVulkanチュートリアルのJava版

Topics

Resources

Stars

Watchers

Forks

Languages

  • Java 99.8%
  • GLSL 0.2%