[译]Vulkan教程(32)生成mipmap
[譯]Vulkan教程(32)生成mipmap
Generating Mipmaps 生成mipmap
Introduction 入門
Our program can now load and render 3D models. In this chapter, we will add one more feature, mipmap generation. Mipmaps are widely used in games and rendering software, and Vulkan gives us complete control over how they are created.
現(xiàn)在我們的程序可以加載和渲染3D模型了。本章,我們將添加一個(gè)特性,mipmap生成。Mipmap廣泛應(yīng)用于游戲和渲染軟件,Vulkan給了我們完全的控制權(quán)-關(guān)于如何創(chuàng)建它們。
Mipmaps are precalculated, downscaled versions of an image. Each new image is half the width and height of the previous one. Mipmaps are used as a form of?Level of Detail?or?LOD.?Objects that are far away from the camera will sample their textures from the smaller mip images. Using smaller images increases the rendering speed and avoids artifacts such as?Moiré patterns. An example of what mipmaps look like:
Mipmap是預(yù)計(jì)算的,縮小版本的image。每個(gè)新image都是是一個(gè)的寬度和高度的一半。Mipmap用于作為Level of Detail(即LOD)的一種方式。遠(yuǎn)離攝像機(jī)的對(duì)象會(huì)從紋理的比較小的mip圖像上采樣。使用更小的image會(huì)增加渲染速度,避免Moiré patterns這樣的鋸齒。一個(gè)mipmap的例子如下:
?
?
Image creation
In Vulkan, each of the mip images is stored in different?mip levels?of a?VkImage. Mip level 0 is the original image, and the mip levels after level 0 are commonly referred to as the?mip chain.
在Vulkan中,每個(gè)mip圖像都保存在VkImage的不同的mip層里。Mip層0是最初的圖像,之后的mip層被稱為mip鏈。
The number of mip levels is specified when the?VkImage?is created. Up until now, we have always set this value to one. We need to calculate the number of mip levels from the dimensions of the image. First, add a class member to store this number:
Mip層的數(shù)量在VkImage?創(chuàng)建時(shí)指定。直到現(xiàn)在,我們總數(shù)設(shè)置這個(gè)值為1。我們需要根據(jù)image的維度計(jì)算mip層的數(shù)量。首先,添加類成員to記錄這個(gè)數(shù):
... uint32_t mipLevels; VkImage textureImage; ...?
The value for?mipLevels?can be found once we've loaded the texture in?createTextureImage:
mipLevels?的值可以在我們?cè)赾reateTextureImage加載了紋理之后立即得到:
int texWidth, texHeight, texChannels; stbi_uc* pixels = stbi_load(TEXTURE_PATH.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); ... mipLevels = static_cast<uint32_t>(std::floor(std::log2(std::max(texWidth, texHeight)))) + 1;?
This calculates the number of levels in the mip chain. The?max?function selects the largest dimension. The?log2 function calculates how many times that dimension can be divided by 2. The?floor?function handles cases where the largest dimension is not a power of 2.?1?is added so that the original image has a mip level.
這計(jì)算了mip鏈中的層的數(shù)量。max?函數(shù)選擇了最大的維度。log2函數(shù)計(jì)算維度可以被2除多少次。floor?函數(shù)處理最大維度不是2的指數(shù)的問題。增加1使得原始圖像有1個(gè)mip層。
To use this value, we need to change the?createImage,?createImageView, and?transitionImageLayout?functions to allow us to specify the number of mip levels. Add a?mipLevels?parameter to the functions:
為使用這個(gè)值,我們需要修改createImage、createImageView和transitionImageLayout?函數(shù)to允許我們指定mip層的數(shù)量。給這些函數(shù)添加mipLevels?參數(shù):
void createImage(uint32_t width, uint32_t height, uint32_t mipLevels, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage& image, VkDeviceMemory& imageMemory) {...imageInfo.mipLevels = mipLevels;... } VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags, uint32_t mipLevels) {...viewInfo.subresourceRange.levelCount = mipLevels;... void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout, uint32_t mipLevels) {...barrier.subresourceRange.levelCount = mipLevels;...?
Update all calls to these functions to use the right values:
更新所有對(duì)這些函數(shù)的調(diào)用,使用正確的值:
createImage(swapChainExtent.width, swapChainExtent.height, 1, depthFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, depthImage, depthImageMemory); ... createImage(texWidth, texHeight, mipLevels, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, textureImage, textureImageMemory); swapChainImageViews[i] = createImageView(swapChainImages[i], swapChainImageFormat, VK_IMAGE_ASPECT_COLOR_BIT, 1); ... depthImageView = createImageView(depthImage, depthFormat, VK_IMAGE_ASPECT_DEPTH_BIT, 1); ... textureImageView = createImageView(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT, mipLevels); transitionImageLayout(depthImage, depthFormat, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, 1); ... transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, mipLevels);?
Generating Mipmaps 生成mipmap
Our texture image now has multiple mip levels, but the staging buffer can only be used to fill mip level 0. The other levels are still undefined. To fill these levels we need to generate the data from the single level that we have. We will use the?vkCmdBlitImage?command. This command performs copying, scaling, and filtering operations. We will call this multiple times to?blit?data to each level of our texture image.
我們的紋理圖像現(xiàn)在有多個(gè)mip層,但是暫存buffer只能用于填充mp層0。其他的層還是未定義的。問填入這些層,我們需要為每個(gè)層生成數(shù)據(jù)。我們要用vkCmdBlitImage?命令。這個(gè)命令試試復(fù)制、縮放和過濾操作。我們多次調(diào)用它來位塊傳送blit數(shù)據(jù)到每個(gè)層。
VkCmdBlit?is considered a transfer operation, so we must inform Vulkan that we intend to use the texture image as both the source and destination of a transfer. Add?VK_IMAGE_USAGE_TRANSFER_SRC_BIT?to the texture image's usage flags in?createTextureImage:
VkCmdBlit?被認(rèn)為是轉(zhuǎn)移操作,所以我們必須通知Vulkan,我們想使用紋理圖像既作為源又作為目標(biāo)。在createTextureImage中添加VK_IMAGE_USAGE_TRANSFER_SRC_BIT?到紋理圖像的用法標(biāo)志:
... createImage(texWidth, texHeight, mipLevels, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, textureImage, textureImageMemory); ...?
Like other image operations,?vkCmdBlitImage?depends on the layout of the image it operates on. We could transition the entire image to?VK_IMAGE_LAYOUT_GENERAL, but this will most likely be slow. For optimal performance, the source image should be in?VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL?and the destination image should be in?VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL. Vulkan allows us to transition each mip level of an image independently. Each blit will only deal with two mip levels at a time, so we can transition each level into the optimal layout between blits commands.
像其他圖像操作一樣,vkCmdBlitImage?依賴于image的布局。我們可以轉(zhuǎn)換整個(gè)image到VK_IMAGE_LAYOUT_GENERAL,但是這會(huì)很慢。為最優(yōu)性能考慮,源image應(yīng)當(dāng)是VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL?,目標(biāo)image應(yīng)當(dāng)是VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL。Vulkan允許我們獨(dú)立地轉(zhuǎn)換每個(gè)mip層。每次blit只會(huì)處理2個(gè)mip層,所以我們可以在兩次blit命令之間轉(zhuǎn)換每個(gè)層到最優(yōu)布局。
transitionImageLayout?only performs layout transitions on the entire image, so we'll need to write a few more pipeline barrier commands. Remove the existing transition to?VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL?in?createTextureImage:
transitionImageLayout?只在整個(gè)image上實(shí)施布局轉(zhuǎn)換,所以我們需要再寫點(diǎn)管道屏障命令。將createTextureImage中已有的轉(zhuǎn)換改為VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL?:
... transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, mipLevels);copyBufferToImage(stagingBuffer, textureImage, static_cast<uint32_t>(texWidth), static_cast<uint32_t>(texHeight)); //transitioned to VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL while generating mipmaps ...?
This will leave each level of the texture image in?VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL. Each level will be transitioned to?VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL?after the blit command reading from it is finished.
這會(huì)讓各個(gè)層處于VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL。在blit命令從層讀取完成后,每個(gè)層會(huì)被轉(zhuǎn)換為VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL?。
We're now going to write the function that generates the mipmaps:
我們現(xiàn)在要寫這個(gè)函數(shù)that生成mipmap:
void generateMipmaps(VkImage image, int32_t texWidth, int32_t texHeight, uint32_t mipLevels) {VkCommandBuffer commandBuffer = beginSingleTimeCommands();VkImageMemoryBarrier barrier = {};barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;barrier.image = image;barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;barrier.subresourceRange.baseArrayLayer = 0;barrier.subresourceRange.layerCount = 1;barrier.subresourceRange.levelCount = 1;endSingleTimeCommands(commandBuffer); }?
We're going to make several transitions, so we'll reuse this?VkImageMemoryBarrier. The fields set above will remain the same for all barriers.?subresourceRange.miplevel,?oldLayout,?newLayout,?srcAccessMask, and?dstAccessMask?will be changed for each transition.
我們要做幾個(gè)轉(zhuǎn)換,所以我們復(fù)用這個(gè)VkImageMemoryBarrier。上述字段的設(shè)置會(huì)對(duì)所有的屏障想通subresourceRange.miplevel、oldLayout、newLayout、srcAccessMask和dstAccessMask?會(huì)隨著每個(gè)轉(zhuǎn)換而改變。
int32_t mipWidth = texWidth; int32_t mipHeight = texHeight;for (uint32_t i = 1; i < mipLevels; i++) {}?
This loop will record each of the?VkCmdBlitImage?commands. Note that the loop variable starts at 1, not 0.
這個(gè)循環(huán)會(huì)錄制每個(gè)VkCmdBlitImage?命令。注意,循環(huán)變量從1開始,不是0。
barrier.subresourceRange.baseMipLevel = i - 1; barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;vkCmdPipelineBarrier(commandBuffer,VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0,0, nullptr,0, nullptr,1, &barrier);?
First, we transition level?i - 1?to?VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL. This transition will wait for level?i - 1?to be filled, either from the previous blit command, or from?vkCmdCopyBufferToImage. The current blit command will wait on this transition.
首先,我們轉(zhuǎn)換層i - 1到VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL。這個(gè)轉(zhuǎn)換會(huì)等待層i - 1被填入,或者來自上一個(gè)blit命令,或者來自vkCmdCopyBufferToImage。當(dāng)前blit命令會(huì)等待這次轉(zhuǎn)換。
VkImageBlit blit = {}; blit.srcOffsets[0] = { 0, 0, 0 }; blit.srcOffsets[1] = { mipWidth, mipHeight, 1 }; blit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; blit.srcSubresource.mipLevel = i - 1; blit.srcSubresource.baseArrayLayer = 0; blit.srcSubresource.layerCount = 1; blit.dstOffsets[0] = { 0, 0, 0 }; blit.dstOffsets[1] = { mipWidth > 1 ? mipWidth / 2 : 1, mipHeight > 1 ? mipHeight / 2 : 1, 1 }; blit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; blit.dstSubresource.mipLevel = i; blit.dstSubresource.baseArrayLayer = 0; blit.dstSubresource.layerCount = 1;?
Next, we specify the regions that will be used in the blit operation. The source mip level is?i - 1?and the destination mip level is?i. The two elements of the?srcOffsets?array determine the 3D region that data will be blitted from.?dstOffsets?determines the region that data will be blitted to. The X and Y dimensions of the?dstOffsets[1]?are divided by two since each mip level is half the size of the previous level. The Z dimension of?srcOffsets[1]?and?dstOffsets[1]?must be 1, since a 2D image has a depth of 1.
接下來,我們知道要被這次blit操作使用的區(qū)域。源mip層是i - 1,目標(biāo)mip層是i。srcOffsets?數(shù)組的2個(gè)元素決定了數(shù)據(jù)會(huì)從哪個(gè)區(qū)域填充。dstOffsets?決定了數(shù)據(jù)會(huì)被填入的區(qū)域。dstOffsets[1]?的X和Y維度被2除,因?yàn)槊總€(gè)mip層都是上一個(gè)的一半。srcOffsets[1]?和dstOffsets[1]的Z維度必須是1,因?yàn)?D圖像的深度就是1。
vkCmdBlitImage(commandBuffer,image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,1, &blit,VK_FILTER_LINEAR);?
Now, we record the blit command. Note that?textureImage?is used for both the?srcImage?and?dstImage parameter. This is because we're blitting between different levels of the same image. The source mip level was just transitioned to?VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL?and the destination level is still in?VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL?from?createTextureImage.
現(xiàn)在,我們錄制blit命令。注意,textureImage?同時(shí)用于srcImage?和dstImage參數(shù)。這是因?yàn)槲覀円谕粓D像的不同層上blit。源mip層被轉(zhuǎn)換為VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL?,目標(biāo)mip層還是從createTextureImage來的VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL?。
The last parameter allows us to specify a?VkFilter?to use in the blit. We have the same filtering options here that we had when making the?VkSampler. We use the?VK_FILTER_LINEAR?to enable interpolation.
最后一個(gè)參數(shù)允許我們指定用于blit的VkFilter?。我們制作時(shí)也使用了相同的過濾選項(xiàng)。我們使用VK_FILTER_LINEAR?來啟用插值。
barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;vkCmdPipelineBarrier(commandBuffer,VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0,0, nullptr,0, nullptr,1, &barrier);?
This barrier transitions mip level?i - 1?to?VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL. This transition waits on the current blit command to finish. All sampling operations will wait on this transition to finish.
這個(gè)屏障轉(zhuǎn)換mip層i - 1為VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL。這個(gè)轉(zhuǎn)換等待當(dāng)前blit命令完成。所有的采樣操作都會(huì)等待這個(gè)轉(zhuǎn)換完成。
...if (mipWidth > 1) mipWidth /= 2;if (mipHeight > 1) mipHeight /= 2; }?
At the end of the loop, we divide the current mip dimensions by two. We check each dimension before the division to ensure that dimension never becomes 0. This handles cases where the image is not square, since one of the mip dimensions would reach 1 before the other dimension. When this happens, that dimension should remain 1 for all remaining levels.
在循環(huán)的結(jié)尾,我們將當(dāng)前mip維度除以2。我們檢查每個(gè)維度before除法,以確保維度不會(huì)成為0。這處理了image不是正方形的情況,因?yàn)槠渲幸粋€(gè)mip維度會(huì)先到達(dá)1。此時(shí),那個(gè)維度就應(yīng)當(dāng)繼續(xù)為1 for剩下的層。
barrier.subresourceRange.baseMipLevel = mipLevels - 1;barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;vkCmdPipelineBarrier(commandBuffer,VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0,0, nullptr,0, nullptr,1, &barrier);endSingleTimeCommands(commandBuffer); }?
Before we end the command buffer, we insert one more pipeline barrier. This barrier transitions the last mip level from?VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL?to?VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL. This wasn't handled by the loop, since the last mip level is never blitted from.
在我們結(jié)束命令buffer前,我們?cè)俨迦胍粋€(gè)管道屏障。這個(gè)屏障轉(zhuǎn)換最后一個(gè)mip層from VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL?to VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL。這沒有被循環(huán)處理,因?yàn)樽詈笠粋€(gè)mip層從沒blit給誰。
Finally, add the call to?generateMipmaps?in?createTextureImage:
最后,在createTextureImage中添加對(duì)generateMipmaps?的調(diào)用:
transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, mipLevels);copyBufferToImage(stagingBuffer, textureImage, static_cast<uint32_t>(texWidth), static_cast<uint32_t>(texHeight)); //transitioned to VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL while generating mipmaps ... generateMipmaps(textureImage, texWidth, texHeight, mipLevels);?
Our texture image's mipmaps are now completely filled.
我們的紋理圖像的mipmap現(xiàn)在就完全填入了。
Linear filtering support 對(duì)線性過濾的支持
It is very convenient to use a built-in function like?vkCmdBlitImage?to generate all the mip levels, but unfortunately it is not guaranteed to be supported on all platforms. It requires the texture image format we use to support linear filtering, which can be checked with the?vkGetPhysicalDeviceFormatProperties?function. We will add a check to the?generateMipmaps?function for this.
使用內(nèi)置函數(shù)如vkCmdBlitImage?來生成所有的mip層是很方便的,但不幸的是,它不保證在所有平臺(tái)上都被支持。它要求紋理圖像格式支持線性過濾,這可以在vkGetPhysicalDeviceFormatProperties?函數(shù)中檢查。我們要添加這樣的檢查到generateMipmaps?函數(shù)。
First add an additional parameter that specifies the image format:
首先添加額外參數(shù)that指定圖像格式:
void createTextureImage() {...generateMipmaps(textureImage, VK_FORMAT_R8G8B8A8_UNORM, texWidth, texHeight, mipLevels); }void generateMipmaps(VkImage image, VkFormat imageFormat, int32_t texWidth, int32_t texHeight, uint32_t mipLevels) {... }?
In the?generateMipmaps?function, use?vkGetPhysicalDeviceFormatProperties?to request the properties of the texture image format:
在函數(shù)中,使用vkGetPhysicalDeviceFormatProperties?檢查紋理圖像格式的屬性:
void generateMipmaps(VkImage image, VkFormat imageFormat, int32_t texWidth, int32_t texHeight, uint32_t mipLevels) {// Check if image format supports linear blitting VkFormatProperties formatProperties;vkGetPhysicalDeviceFormatProperties(physicalDevice, imageFormat, &formatProperties);...?
The?VkFormatProperties?struct has three fields named?linearTilingFeatures,?optimalTilingFeatures?and?bufferFeatures?that each describe how the format can be used depending on the way it is used. We create a texture image with the optimal tiling format, so we need to check?optimalTilingFeatures. Support for the linear filtering feature can be checked with the?VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT:
VkFormatProperties?結(jié)構(gòu)體有3個(gè)字段,linearTilingFeatures、optimalTilingFeatures?和bufferFeatures?,根據(jù)格式的使用方式,描述格式如何被使用。我們創(chuàng)建一個(gè)最優(yōu)tiling格式的紋理圖像,所以我們需要檢查optimalTilingFeatures。對(duì)線性過濾特性的支持可以用VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT查詢:
if (!(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT)) {throw std::runtime_error("texture image format does not support linear blitting!"); }?
There are two alternatives in this case. You could implement a function that searches common texture image formats for one that?does?support linear blitting, or you could implement the mipmap generation in software with a library like?stb_image_resize. Each mip level can then be loaded into the image in the same way that you loaded the original image.
此時(shí)有2個(gè)選項(xiàng)。你可以實(shí)現(xiàn)一個(gè)函數(shù)that搜索場(chǎng)景的紋理圖像格式,找一個(gè)支持線性過濾的,或者你可以用一個(gè)庫來軟實(shí)現(xiàn)mipmap生成,像stb_image_resize一樣。然后每個(gè)mip層就可以被加載到image,就像你加載原始image那樣。
It should be noted that it is uncommon in practice to generate the mipmap levels at runtime anyway. Usually they are pregenerated and stored in the texture file alongside the base level to improve loading speed. Implementing resizing in software and loading multiple levels from a file is left as an exercise to the reader.
要注意到,實(shí)踐中不常在運(yùn)行時(shí)生成mipmap層的方式。一般的,它們都是預(yù)生成了,保存到紋理文件里to提升加載速度。軟件實(shí)現(xiàn)resize和加載多mip層就留給讀者作為練習(xí)了。
Sampler 采樣器
While the?VkImage?holds the mipmap data,?VkSampler?controls how that data is read while rendering. Vulkan allows us to specify?minLod,?maxLod,?mipLodBias, and?mipmapMode?("Lod" means "Level of Detail"). When a texture is sampled, the sampler selects a mip level according to the following pseudocode:
VkImage?記錄了mipmap數(shù)據(jù),但VkSampler?控制了渲染時(shí)數(shù)據(jù)如何被讀取。Vulkan允許我們指定minLod、maxLod、mipLodBias和mipmapMode?(Lod的意思是Level of Detail)。當(dāng)一個(gè)紋理被采樣時(shí),采樣器根據(jù)下述偽代碼選擇一個(gè)mip層:
lod = getLodLevelFromScreenSize(); //smaller when the object is close, may be negative lod = clamp(lod + mipLodBias, minLod, maxLod);level = clamp(floor(lod), 0, texture.mipLevels - 1); //clamped to the number of mip levels in the textureif (mipmapMode == VK_SAMPLER_MIPMAP_MODE_NEAREST) {color = sample(level); } else {color = blend(sample(level), sample(level + 1)); }?
If?samplerInfo.mipmapMode?is?VK_SAMPLER_MIPMAP_MODE_NEAREST,?lod?selects the mip level to sample from. If the mipmap mode is?VK_SAMPLER_MIPMAP_MODE_LINEAR,?lod?is used to select two mip levels to be sampled. Those levels are sampled and the results are linearly blended.
如果samplerInfo.mipmapMode?是VK_SAMPLER_MIPMAP_MODE_NEAREST,lod?選擇mip層去采樣。如果mipmap模式是VK_SAMPLER_MIPMAP_MODE_LINEAR,lod?用于選擇2個(gè)mi層來采樣。這些層被采樣,結(jié)果被線性混合。
The sample operation is also affected by?lod:
采樣操作也被lod影響:
if (lod <= 0) {color = readTexture(uv, magFilter); } else {color = readTexture(uv, minFilter); }?
If the object is close to the camera,?magFilter?is used as the filter. If the object is further from the camera,?minFilter?is used. Normally,?lod?is non-negative, and is only 0 when close the camera.?mipLodBias?lets us force Vulkan to use lower?lod?and?level?than it would normally use.
如果對(duì)象距離攝像機(jī)很近,magFilter?就用于過濾。如果對(duì)象距離攝像機(jī)很遠(yuǎn),minFilter?就用上了。一般地,lod?是非負(fù)數(shù),只有接近攝像機(jī)時(shí)才為0。mipLodBias?讓我們強(qiáng)制Vulkan使用比較低的lod?和level? than它一般用的。
To see the results of this chapter, we need to choose values for our?textureSampler. We've already set the?minFilter?and?magFilter?to use?VK_FILTER_LINEAR. We just need to choose values for?minLod,?maxLod,?mipLodBias, and?mipmapMode.
為了看看本章的結(jié)果,我們需要選擇我們的textureSampler值,我們已經(jīng)設(shè)置了minFilter?和magFilter? to使用VK_FILTER_LINEAR。我們只需選擇minLod、maxLod、mipLodBias和mipmapMode的值。
void createTextureSampler() {...samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;samplerInfo.minLod = 0; // OptionalsamplerInfo.maxLod = static_cast<float>(mipLevels);samplerInfo.mipLodBias = 0; // Optional ... }?
To allow the full range of mip levels to be used, we set?minLod?to 0, and?maxLod?to the number of mip levels. We have no reason to change the?lod?value , so we set?mipLodBias?to 0.
為了使用全部范圍內(nèi)的mip層,我們?cè)O(shè)置minLod?為0,設(shè)置maxLod?為mip層的數(shù)量。我們沒有理由修改lod?值,所以我們?cè)O(shè)置mipLodBias?為0。
Now run your program and you should see the following:
現(xiàn)在運(yùn)行你的程序,你應(yīng)當(dāng)看到下述情景:
?
?
It's not a dramatic difference, since our scene is so simple. There are subtle differences if you look closely.
沒什么打的區(qū)別,因?yàn)槲覀兊膱?chǎng)景太簡(jiǎn)單了。如果你靠近觀看,會(huì)有微妙的區(qū)別。
?
?
The most noticeable difference is the writing on the signs. With mipmaps, the writing has been smoothed. Without mipmaps, the writing has harsh edges and gaps from Moiré artifacts.
最引人注意的區(qū)別是。有mipmap,寫入被平滑了。沒有mipmap,Moiré藝術(shù)品寫入會(huì)有刺目的邊界和裂縫。
You can play around with the sampler settings to see how they affect mipmapping. For example, by changing?minLod, you can force the sampler to not use the lowest mip levels:
你可以鼓搗鼓搗采樣器設(shè)置to看看它們?nèi)绾斡绊憁ipmap。例如,通過修改minLod,你可以強(qiáng)制采樣器不使用最低的mip層:
samplerInfo.minLod = static_cast<float>(mipLevels / 2);?
These settings will produce this image:
這些設(shè)置會(huì)產(chǎn)生這樣的結(jié)果:
?
?
This is how higher mip levels will be used when objects are further away from the camera.
這就是更高的mip層會(huì)被使用的結(jié)果when對(duì)象原理攝像機(jī)。
C++ code?/?Vertex shader?/?Fragment shader
- Previous
?
- Next
?
轉(zhuǎn)載于:https://www.cnblogs.com/bitzhuwei/p/Vulkan-Tutorial-32-Generating-Mipmaps.html
總結(jié)
以上是生活随笔為你收集整理的[译]Vulkan教程(32)生成mipmap的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 探秘大型B2C网站如何实现高性能可伸缩架
- 下一篇: node.js常见的模块