[譯]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.
現在我們的程序可以加載和渲染3D模型了。本章,我們將添加一個特性,mipmap生成。Mipmap廣泛應用於游戲和渲染軟件,Vulkan給了我們完全的控制權-關於如何創建它們。
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是預計算的,縮小版本的image。每個新image都是是一個的寬度和高度的一半。Mipmap用於作為Level of Detail(即LOD)的一種方式。遠離攝像機的對象會從紋理的比較小的mip圖像上采樣。使用更小的image會增加渲染速度,避免Moiré patterns這樣的鋸齒。一個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中,每個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層的數量在VkImage
創建時指定。直到現在,我們總數設置這個值為1。我們需要根據image的維度計算mip層的數量。首先,添加類成員to記錄這個數:
...
uint32_t mipLevels;
VkImage textureImage;
...
The value for mipLevels
can be found once we've loaded the texture in createTextureImage
:
mipLevels
的值可以在我們在createTextureImage
加載了紋理之后立即得到:
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.
這計算了mip鏈中的層的數量。max
函數選擇了最大的維度。log2
函數計算維度可以被2除多少次。floor
函數處理最大維度不是2的指數的問題。增加1
使得原始圖像有1個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:
為使用這個值,我們需要修改createImage
、createImageView
和transitionImageLayout
函數to允許我們指定mip層的數量。給這些函數添加mipLevels
參數:
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:
更新所有對這些函數的調用,使用正確的值:
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.
我們的紋理圖像現在有多個mip層,但是暫存buffer只能用於填充mp層0。其他的層還是未定義的。問填入這些層,我們需要為每個層生成數據。我們要用vkCmdBlitImage
命令。這個命令試試復制、縮放和過濾操作。我們多次調用它來位塊傳送blit數據到每個層。
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
被認為是轉移操作,所以我們必須通知Vulkan,我們想使用紋理圖像既作為源又作為目標。在createTextureImage
中添加VK_IMAGE_USAGE_TRANSFER_SRC_BIT
到紋理圖像的用法標志:
... 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的布局。我們可以轉換整個image到VK_IMAGE_LAYOUT_GENERAL
,但是這會很慢。為最優性能考慮,源image應當是VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL
,目標image應當是VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL
。Vulkan允許我們獨立地轉換每個mip層。每次blit只會處理2個mip層,所以我們可以在兩次blit命令之間轉換每個層到最優布局。
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
只在整個image上實施布局轉換,所以我們需要再寫點管道屏障命令。將createTextureImage
中已有的轉換改為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.
這會讓各個層處於VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL
。在blit命令從層讀取完成后,每個層會被轉換為VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
。
We're now going to write the function that generates the mipmaps:
我們現在要寫這個函數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.
我們要做幾個轉換,所以我們復用這個VkImageMemoryBarrier
。上述字段的設置會對所有的屏障想通subresourceRange.miplevel
、oldLayout
、newLayout
、srcAccessMask
和dstAccessMask
會隨着每個轉換而改變。
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.
這個循環會錄制每個VkCmdBlitImage
命令。注意,循環變量從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.
首先,我們轉換層i - 1
到VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL
。這個轉換會等待層i - 1
被填入,或者來自上一個blit命令,或者來自vkCmdCopyBufferToImage
。當前blit命令會等待這次轉換。
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操作使用的區域。源mip層是i - 1
,目標mip層是i
。srcOffsets
數組的2個元素決定了數據會從哪個區域填充。dstOffsets
決定了數據會被填入的區域。dstOffsets[1]
的X和Y維度被2除,因為每個mip層都是上一個的一半。srcOffsets[1]
和dstOffsets[1]
的Z維度必須是1,因為2D圖像的深度就是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
.
現在,我們錄制blit命令。注意,textureImage
同時用於srcImage
和dstImage
參數。這是因為我們要在同一圖像的不同層上blit。源mip層被轉換為VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL
,目標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.
最后一個參數允許我們指定用於blit的VkFilter
。我們制作時也使用了相同的過濾選項。我們使用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.
這個屏障轉換mip層i - 1
為VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
。這個轉換等待當前blit命令完成。所有的采樣操作都會等待這個轉換完成。
... 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.
在循環的結尾,我們將當前mip維度除以2。我們檢查每個維度before除法,以確保維度不會成為0。這處理了image不是正方形的情況,因為其中一個mip維度會先到達1。此時,那個維度就應當繼續為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.
在我們結束命令buffer前,我們再插入一個管道屏障。這個屏障轉換最后一個mip層from VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL
to VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
。這沒有被循環處理,因為最后一個mip層從沒blit給誰。
Finally, add the call to generateMipmaps
in createTextureImage
:
最后,在createTextureImage
中添加對generateMipmaps
的調用:
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現在就完全填入了。
Linear filtering support 對線性過濾的支持
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.
使用內置函數如vkCmdBlitImage
來生成所有的mip層是很方便的,但不幸的是,它不保證在所有平台上都被支持。它要求紋理圖像格式支持線性過濾,這可以在vkGetPhysicalDeviceFormatProperties
函數中檢查。我們要添加這樣的檢查到generateMipmaps
函數。
First add an additional parameter that specifies the image format:
首先添加額外參數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:
在函數中,使用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
結構體有3個字段,linearTilingFeatures
、optimalTilingFeatures
和bufferFeatures
,根據格式的使用方式,描述格式如何被使用。我們創建一個最優tiling格式的紋理圖像,所以我們需要檢查optimalTilingFeatures
。對線性過濾特性的支持可以用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.
此時有2個選項。你可以實現一個函數that搜索場景的紋理圖像格式,找一個支持線性過濾的,或者你可以用一個庫來軟實現mipmap生成,像stb_image_resize一樣。然后每個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.
要注意到,實踐中不常在運行時生成mipmap層的方式。一般的,它們都是預生成了,保存到紋理文件里to提升加載速度。軟件實現resize和加載多mip層就留給讀者作為練習了。
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數據,但VkSampler
控制了渲染時數據如何被讀取。Vulkan允許我們指定minLod
、maxLod
、mipLodBias
和mipmapMode
(Lod的意思是Level of Detail)。當一個紋理被采樣時,采樣器根據下述偽代碼選擇一個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 texture if (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個mi層來采樣。這些層被采樣,結果被線性混合。
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.
如果對象距離攝像機很近,magFilter
就用於過濾。如果對象距離攝像機很遠,minFilter
就用上了。一般地,lod
是非負數,只有接近攝像機時才為0。mipLodBias
讓我們強制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
.
為了看看本章的結果,我們需要選擇我們的textureSampler
值,我們已經設置了minFilter
和magFilter
to使用VK_FILTER_LINEAR
。我們只需選擇minLod
、maxLod
、mipLodBias
和mipmapMode
的值。
void createTextureSampler() { ... samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; samplerInfo.minLod = 0; // Optional samplerInfo.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.
為了使用全部范圍內的mip層,我們設置minLod
為0,設置maxLod
為mip層的數量。我們沒有理由修改lod
值,所以我們設置mipLodBias
為0。
Now run your program and you should see the following:
現在運行你的程序,你應當看到下述情景:
It's not a dramatic difference, since our scene is so simple. There are subtle differences if you look closely.
沒什么打的區別,因為我們的場景太簡單了。如果你靠近觀看,會有微妙的區別。
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.
最引人注意的區別是。有mipmap,寫入被平滑了。沒有mipmap,Moiré藝術品寫入會有刺目的邊界和裂縫。
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:
你可以鼓搗鼓搗采樣器設置to看看它們如何影響mipmap。例如,通過修改minLod
,你可以強制采樣器不使用最低的mip層:
samplerInfo.minLod = static_cast<float>(mipLevels / 2);
These settings will produce this image:
這些設置會產生這樣的結果:
This is how higher mip levels will be used when objects are further away from the camera.
這就是更高的mip層會被使用的結果when對象原理攝像機。
C++ code / Vertex shader / Fragment shader