1. 程式人生 > >《權限系列shiro+cas》---封裝公共驗證模塊

《權限系列shiro+cas》---封裝公共驗證模塊

t對象 mode 轉換 關於 設計 下一步 lock 倒置 做的

  操作系統:Windows8.1
  
  顯卡:Nivida GTX965M
  
  開發工具:Visual Studio 2017
  
  Introduction
  
  我們現在可以將任意屬性傳遞給每個頂點的頂點著色器使用。但是全局變量呢?我們將會從本章開始介紹3D圖形相關的內容,並需要一個模型視圖投影矩陣。我們確實可以將它一頂點的方式包含,但是這非常浪費帶寬、內存,並且需要我們在變換的時候更新頂點緩沖區的數據。這種變換通常發生在每一幀。
  
  在Vulkan中正確處理此問題的途徑是使用資源描述符。描述符是著色器資源訪問諸如緩沖區和圖像這樣資源的一種方式。我們需要設置一個包含轉換矩陣的緩沖區,並使頂點著色器通過描述符訪問它們。描述符的使用由三部分組成:
  
  在管線創建時指定描述符的布局結構
  
  從描述符對象池中分配描述符集合
  
  在渲染階段綁定描述符集合
  
  描述符布局指定了管線訪問的資源的類型,就像渲染通道指定附件的類型一樣。描述符集合指定將綁定到描述符的實際緩沖區或映射資源。就像幀緩沖區為綁定渲染通道的附件而指定實際的圖像視圖一樣。描述符集合就像頂點緩沖區和幀緩沖區一樣被綁定到繪制命令。
  
  有許多類型的描述符,但在本章中,我們將使用統一的緩沖區對象uniform buffer objects(UBO)。我們將會在後面的章節中討論其他類型的描述符,但基本過程是一樣的。假設我們有一個數據,我們希望頂點著色器擁有一個這樣的C結構體:
  
  struct UniformBufferObject {
  
  glm::mat4 model;
  
  glm::mat4 view;
  
  glm::mat4 proj;
  
  };
  
  我們可以拷貝數據到VkBuffer並在頂點著色器中通過uniform buffer object描述訪問它們:
  
  復制代碼
  
  layout(binding = 0) uniform UniformBufferObject {
  
  mat4 model;
  
  mat4 view;
  
  mat4 proj;
  
  } ubo;
  
  void main() {
  
  gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 0.0, 1.0);
  
  fragColor = inColor;
  
  }
  
  復制代碼
  
  我們會在每一幀更新模型,視圖和投影矩陣,使前一章的矩形以3D旋轉。
  
  Vertex shader
  
  修改頂點著色器包含像上面指定的統一緩沖區對象uniform buffer object。假設大家比較熟悉MVP變換。如果不是這樣,可以看第一章中提到的內容了解。
  
  復制代碼
  
  #version 450
  
  #extension GL_ARB_separate_shader_objects : enable
  
  layout(binding = 0) uniform UniformBufferObject {
  
  mat4 model;
  
  mat4 view;
  
  mat4 proj;
  
  } ubo;
  
  layout(location = 0) in vec2 inPosition;
  
  layout(location = 1) in vec3 inColor;
  
  layout(location = 0) out vec3 fragColor;
  
  out gl_PerVertex {
  
  vec4 gl_Position;
  
  };
  
  void main() {
  
  gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 0.0, 1.0);
  
  fragColor = inColor;
  
  }
  
  復制代碼
  
  需要註意的是uniform, in 和 out它們聲明的順序無關緊要。binding指令與location屬性指令類似。我們將在描述符布局中引用此綁定。gl_Position使用變換矩陣計算裁剪坐標中最終的位置信息。
  
  Descriptor set layout
  
  下一步在C++應用層定義UBO數據結構,並告知Vulkan在頂點著色器使用該描述符。
  
  struct UniformBufferObject {
  
  glm::mat4 model;
  
  glm::mat4 view;
  
  glm::mat4 proj;
  
  };
  
  我們可以使用GLM中的與著色器中結構體完全匹配的數據類型。矩陣中的數據與著色器預期的二進制數據兼容,所以我們可以稍後將一個UniformBufferObject通過memcpy拷貝到VkBuffer中。
  
  我們需要在管線創建時,為著色器提供關於每個描述符綁定的詳細信息,就像我們為每個頂點屬性和location索引做的一樣。我們添加一個新的函數來定義所有這些名為createDescritorSetLayout的信息。考慮到我們會在管線中使用,它應該在管線創建函數之前調用。
  
  復制代碼
  
  void initVulkan() {
  
  ...
  
  createDescriptorSetLayout();
  
  createGraphicsPipeline();
  
  ...
  
  }
  
  ...
  
  void createDescriptorSetLayout() {
  
  }
  
  復制代碼
  
  每個綁定都會通過VkDescriptorSetLayoutBinding結構體描述。
  
  void createDescriptorSetLayout(www.wmylcs.com ) {
  
  VkDescriptorSetLayoutBinding uboLayoutBinding = {};
  
  uboLayoutBinding.binding = 0;
  
  uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
  
  uboLayoutBinding.descriptorCount = 1;
  
  }
  
  前兩個字段指定著色器中使用的binding和描述符類型,它是一個UBO。著色器變量可以表示UBO數組,descriptorCount指定數組中的數值。比如,這可以用於骨骼動畫中的每個骨骼變換。我們的MVP 變換是一個單UBO對象,所以我們使用descriptorCount為1。
  
  uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
  
  我們也需要指定描述符在著色器哪個階段被引用。stageFlags字段可以是VkShaderStage標誌位或VK_SHADER_STAGE_ALL_GRAPHICS的組合。在我們的例子中,我們僅僅在頂點著色器中使用描述符。
  
  uboLayoutBinding.pImmutableSamplers = nullptr; /www.wmyl88.com// Optional
  
  pImmutableSamplers字段僅僅與圖像采樣的描述符有關,我們會在後面的內容討論。現在可以設置為默認值。
  
  所有的描述符綁定都會被組合在一個單獨的VkDescriptorSetLa www.wmyl11.com/ yout對象。定義一個新的類成員變量pipelineLayout:
  
  VkDescriptorSetLayout descript www.feifanshifan8.cn orSetLayout;
  
  VkPipelineLayout pipelineLayout;
  
  我們使用vkCreateDescriptorSetLayout創建。這個函數接受一個簡單的結構體VkDescriptorSetLayoutCreateInfo,該結構體持有一個綁定數組。
  
  復制代碼
  
  VkDescriptorSetLayoutCreateInfo www.wmyl15.com/ layoutInfo = {};
  
  layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
  
  layoutInfo.bindingCount = 1;
  
  layoutInfo.pBindings = &uboLayoutBinding;
  
  if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &descriptorSetLayout) != VK_SUCCESS) {
  
  throw std::runtime_error("failed to create descriptor set layout!");
  
  }
  
  復制代碼
  
  我們需要在創建管線的時候指定描述符集合的布局,用以告知Vulkan著色器將要使用的描述符。描述符布局在管線布局對象中指定。修改VkPipelineLayoutCreateInfo引用布局對象:
  
  VkPipelineLayoutCreateInfo pipelineLayoutInfo = {};
  
  pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
  
  pipelineLayoutInfo.setLayoutCount = 1;
  
  pipelineLayoutInfo.pSetLayouts = &descriptorSetLayout;
  
  到這裏可能會有疑問,為什麽可以在這裏指定那麽多的描述符布局集合,因為一個包含了所有的綁定。我們將在下一章回顧一下,我們將在其中查看描述符對象池和描述符集合。
  
  描述符布局應該在程序退出前始終有效:
  
  復制代碼
  
  void cleanup() {
  
  cleanupSwapChain();
  
  vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
  
  ...
  
  }
  
  復制代碼
  
  Uniform buffer
  
  在下一章節我們會為著色器重點包含UBO的緩沖區,但是首先要創建該緩沖區。在每一幀中我們需要拷貝新的數據到UBO緩沖區,所以存在一個暫存緩沖區是沒有意義的。在這種情況下,它只會增加額外的開銷,並且可能降低性能而不是提升性能。
  
  添加類成員uniformBuffer和uniformBufferMemory:
  
  VkBuffer indexBuffer;
  
  VkDeviceMemory indexBufferMemory;
  
  VkBuffer uniformBuffer;
  
  VkDeviceMemory uniformBufferMemory;
  
  同樣需要添加新的函數createUniformBuffer來分配緩沖區,並在createIndexBuffer之後調用。
  
  復制代碼
  
  void initVulkan() {
  
  ...
  
  createVertexBuffer();
  
  createIndexBuffer();
  
  createUniformBuffer();
  
  ...
  
  }
  
  ...
  
  void createUniformBuffer() {
  
  VkDeviceSize bufferSize = sizeof(UniformBufferObject);
  
  createBuffer(bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, uniformBuffer, uniformBufferMemory);
  
  }
  
  復制代碼
  
  我們要寫一個單獨的函數來更新uniform緩沖區,確保每一幀都有更新,所以在這裏不會有vkMapMemory。UBO的數據將被用於所有的繪制使用,所以包含它的緩沖區只能在最後銷毀:
  
  復制代碼
  
  void cleanup() {
  
  cleanupSwapChain();
  
  vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
  
  vkDestroyBuffer(device, uniformBuffer, nullptr);
  
  vkFreeMemory(device, uniformBufferMemory, nullptr);
  
  ...
  
  }
  
  復制代碼
  
  Updating uniform data
  
  添加新的函數updateUniformBuffer並在main loop中調用:
  
  復制代碼
  
  void mainLoop() {
  
  while (!glfwWindowShouldClose(window)) {
  
  glfwPollEvents();
  
  updateUniformBuffer();
  
  drawFrame();
  
  }
  
  vkDeviceWaitIdle(device);
  
  }
  
  ...
  
  void updateUniformBuffer() {
  
  }
  
  復制代碼
  
  該函數會在每一幀中創建新的變換矩陣以確保幾何圖形旋轉。我們需要引入新的頭文件使用該功能:
  
  #define GLM_FORCE_RADIANS
  
  #include <glm/glm.hpp>
  
  #include <glm/gtc/matrix_transform.hpp>
  
  #include <chrono>
  
  glm/gtc/matrix_transform.cpp頭文件對外提供用於生成模型變換矩陣的gl::rotate,視圖變換矩陣的 glm:lookAt 和 投影變換矩陣的 glm::perspective。GLM_FORCE_RADIANS定義是必要的,它確保像 glm::rotate 這樣的函數使用弧度制作為參數,以避免任何可能的混淆。
  
  chrono標準庫的頭文件對外提供計時功能。我們將使用它來確保集合旋轉每秒90度,無論幀率如何。
  
  void updateUniformBuffer() {
  
  static auto startTime = std::chrono::high_resolution_clock::now();
  
  auto currentTime = std::chrono::high_resolution_clock::now();
  
  float time = std::chrono::duration_cast<std::chrono::milliseconds>(currentTime - startTime).count() / 1000.0f;
  
  }
  
  updateUniformBuffer函數將有關時間計算的邏輯開始,它會以毫秒級的精度計算渲染開始後的時間(秒為單位)。如果需要更準確的時間,則可以使用 std::chrono::microseconds 並除以 1e6f,這是 1000000.0 的縮寫。
  
  我們在UBO中定義模型,視圖和投影變換矩陣。模型變換將會圍繞Z-axis旋轉,並使用 time 變量更新旋轉角度:
  
  UniformBufferObject ubo = {};
  
  ubo.model = glm::rotate(glm::mat4(), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f));
  
  glm::rotate 函數對現有的變換矩陣進行旋轉,它使用旋轉角度和旋轉軸作為參數。glm::mat4() 默認的構造返回歸一化的矩陣。使用 time * glm::radians(90.0f) 可以實現每秒90度的旋轉目的。
  
  ubo.view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f));
  
  對於視圖變換,我們決定以45度從上觀察幾何圖形。glm::lookAt 函數以眼睛位置,中心位置和上方向為參數。
  
  ubo.proj = glm::perspective(glm::radians(45.0f), swapChainExtent.width / (float) swapChainExtent.height, 0.1f, 10.0f);
  
  選擇使用FOV為45度的透視投影。其他參數是寬高比,近裁剪面和遠裁剪面。重要的是使用當前的交換鏈擴展來計算寬高比,以便在窗體調整大小後參考最新的窗體寬度和高度。
  
  ubo.proj[1][1] *= -1;
  
  GLM最初是為OpenGL設計的,它的裁剪坐標的Y是反轉的。修正該問題的最簡單的方法是在投影矩陣中Y軸的縮放因子反轉。如果不這樣做圖像會被倒置。
  
  現在定義了所有的變換,所以我們可以將UBO中的數據復制到uniform緩沖區。這與我們對頂點緩沖區的操作完全相同,除了沒有暫存緩沖區:
  
  void* data;
  
  vkMapMemory(device, uniformBufferMemory, 0, sizeof(ubo), 0, &data);
  
  memcpy(data, &ubo, sizeof(ubo));
  
  vkUnmapMemory(device, uniformBufferMemory);
  
  使用UBO將並不是經常變化的值傳遞給著色器是非常有效的方式。相比傳遞一個更小的數據緩沖區到著色器中,更有有效的方式是使用常量。我們在未來的章節中會看到這些。

《權限系列shiro+cas》---封裝公共驗證模塊