三角形を回す
前回3角形を描画するという大きな1歩を踏み出しました。今回はこの三角形を回転させたいと思います。
前回の内容に比べると新規の内容は比較的少なめです。
GlmSharpの導入
行列の演算のために GlmSharp を使用します。 これは NuGet で取得してください。
定数バッファの準備
前回の頂点バッファと同じように、今度は定数バッファを作成します。
private void CreateUniformBuffer(VkDevice device, VkPhysicalDevice physicalDevice) { var bufferSize = Marshal.SizeOf(); VkMemoryPropertyFlags memoryFlags = VkMemoryPropertyFlags.VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VkMemoryPropertyFlags.VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; SampleHelpers.CreateBuffer(device, physicalDevice, bufferSize, VkBufferUsageFlags.VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, memoryFlags, out m_uniformBuffer, out m_uniformBufferMemory); }
定数バッファを描画で使用するためには、ディスクリプタ関連およびパイプラインレイアウトの設定が追加で必要になります。まずはディスクリプタのほうから確認していきます。
ディスクリプタ
今回は定数バッファを1つ使うだけなので、その設定でディスクリプタプールを以下のように準備しました。
VkDescriptorPoolSize descriptorPoolSize = new VkDescriptorPoolSize() { descriptorCount = 1, type = VkDescriptorType.VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, }; var descriptorPoolCreateInfo = new VkDescriptorPoolCreateInfo() { poolSizes = new[] { descriptorPoolSize }, maxSets = 1, flags = VkDescriptorPoolCreateFlags.VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, }; VulkanAPI.vkCreateDescriptorPool(device, ref descriptorPoolCreateInfo, out m_descriptorPool);
続いてディスクリプタセットレイアウトの作成です。これも定数バッファを1つだけ持つという構成で作成します。
このレイアウトとはシェーダーでどのようにリソースがセットされるのかといった指示書情報のようなものです(たぶん)。
// ディスクリプタセットレイアウトの作成. // 今は定数バッファを1つ持つだけのものを作成. var descriptorLayoutBinding = new VkDescriptorSetLayoutBinding(); descriptorLayoutBinding.binding = 0; descriptorLayoutBinding.descriptorCount = 1; descriptorLayoutBinding.descriptorType = VkDescriptorType.VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; descriptorLayoutBinding.stageFlags = VkShaderStageFlagBits.VK_SHADER_STAGE_VERTEX_BIT; var descriptorSetLayoutCreateInfo = new VkDescriptorSetLayoutCreateInfo(); descriptorSetLayoutCreateInfo.bindings = new[] { descriptorLayoutBinding }; VulkanAPI.vkCreateDescriptorSetLayout(device, ref descriptorSetLayoutCreateInfo, out m_descriptorSetLayout); // ディスクリプタセットを作成. VkDescriptorSet[] descriptorSets; var descriptorSetAllocateInfo = new VkDescriptorSetAllocateInfo(m_descriptorPool, new[] { m_descriptorSetLayout }); VulkanAPI.vkAllocateDescriptorSets(device, ref descriptorSetAllocateInfo, out descriptorSets); m_descriptorSet = descriptorSets[0]; // ディスクリプタを更新. // 定数バッファ1つを持つレイアウトでディスクリプタを作る. VkDescriptorBufferInfo descUniformBufferInfo = new VkDescriptorBufferInfo(); descUniformBufferInfo.buffer = m_uniformBuffer; descUniformBufferInfo.range = Marshal.SizeOf(); var descriptor = new VkWriteDescriptorSet(); descriptor.descriptorCount = 1; descriptor.descriptorType = VkDescriptorType.VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; descriptor.pBufferInfo = new[] { descUniformBufferInfo }; descriptor.dstBinding = 0; descriptor.dstSet = m_descriptorSet; VulkanAPI.vkUpdateDescriptorSets(device, new[] { descriptor }, null);
パイプラインレイアウトの設定
前回はデフォルトのままなにも中身を詰めなかったパイプラインレイアウトですが、
今回ディスクリプタを使うようになったためパイプラインレイアウトの設定が必要になります。
private VkPipelineLayout CreatePipelineLayout(VkDevice device) { var createInfo = new VkPipelineLayoutCreateInfo(); createInfo.setLayouts = new[] { m_descriptorSetLayout }; VkPipelineLayout layout = null; VulkanAPI.vkCreatePipelineLayout(device, ref createInfo, out layout); return layout; }
このように依存する情報が色々とあるので、リソースデータの作成順序を見直す必要がそろそろ出てきそうです。
今回のサンプルでは、頂点バッファ生成・定数バッファ生成・ディスクリプタ準備と始めて、
その後に、グラフィックスパイプラインを構築するための他の情報を準備していくという方法にしました。
この辺りの詳細はサンプルコードの方を見て頂ければと思います。
描画処理
定数バッファの中には、ワールド、ビュー、プロジェクションの行列を格納することにしています。
この定数バッファの中身を更新して三角形を回転させます。
定数バッファへの書込も、初期頂点データの書き込みの方法と全く同じ手順となります。
GlmSharp を使って、各行列を計算させています。
// 定数バッファの更新. var sceneTrs = new Transform(); var aspect = (float)currentExtent.width / (float)currentExtent.height; var proj = mat4.Perspective((float)Math.PI / 3.0f, aspect, 1.0f, 100.0f); sceneTrs.World = mat4.RotateY(m_frameCount * 0.1f); sceneTrs.View = mat4.LookAt(new vec3(0, 0, 3), new vec3(0, 0, 0), new vec3(0, 1, 0)); sceneTrs.Proj = proj; MappedMemoryStream mapped = null; VulkanAPI.vkMapMemory(device, m_uniformBufferMemory, 0, VkDeviceSize.VK_WHOLE_SIZE, 0, out mapped); mapped.Write(sceneTrs); VulkanAPI.vkUnmapMemory(device, m_uniformBufferMemory);
この定数バッファをシェーダーで使うためにはコマンドを積む必要があります。
描画を行う前に以下のようにセットを行います。
VulkanAPI.vkCmdBindDescriptorSets( command, VkPipelineBindPoint.VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineLayout, 0, new[] { m_descriptorSet }, null);
シェーダーコードは以下のようになっています。今回は定数バッファを使うのが頂点シェーダーなのでこちらだけ変更しました。
#version 450 layout(location=0) in vec3 InPos; layout(location=1) in vec4 InCol; layout(location=0) out vec4 OutCol; layout(binding=0) uniform Transform { mat4 world; mat4 view; mat4 proj; } transform; out gl_PerVertex { vec4 gl_Position; }; void main() { mat4 pv = transform.proj * transform.view; gl_Position = pv * transform.world * vec4(InPos, 1.0); OutCol = InCol; }
定数バッファは、0番目に設定されるようにディスクリプタセットを構築したので binding = 0 となっています。
一見 OpenGL でも通りそうな GLSL ですが、 Vulkan 用 GLSL のコードと見分けるポイントの1つでしょうか。
まとめ
ディスクリプタの部分は今までの OpenGL/DirectX では出てこなかった部分なので、難しいかと思います。
ここでも詳しく説明しませんでした。うまく説明されているサイト様に期待したいところです。
ディスクリプタ関連を変えるとパイプラインレイアウトを変える必要が出てきます。これはグラフィックスパイプラインの再構築まで波及します。
今まで以上に、構成を変更した時のソースコードの変更箇所が多くなるという点にご注意ください。
サンプルコード
リポジトリはこちらになります。 https://github.com/techlabxe/VulkanSharpTutorial
この中にある 04_WorldTriangle が今回の内容と合致するものとなっています。