[译]Vulkan教程(28)Image视图和采样器
[译]Vulkan教程(28)Image视图和采样器
Image view and sampler - Image视图和采样器
In this chapter we're going to create two more resources that are needed for the graphics pipeline to sample an image. The first resource is one that we've already seen before while working with the swap chain images, but the second one is new - it relates to how the shader will read texels from the image.
本章我们要创建2个资源that为采样image而被图形管道需要。第一个资源是我们之前用交换链image时见过的,但是第二个是新的——它与shader如何从image中读取纹素有关。
Texture image view 纹理image视图
We've seen before, with the swap chain images and the framebuffer, that images are accessed through image views rather than directly. We will also need to create such an image view for the texture image.
我们见过,在交换链image和帧缓存中,image是通过image视图存取的,不是直接存取的。我们也需要创建这样的image视图for纹理image。
Add a class member to hold a VkImageView
for the texture image and create a new function createTextureImageView
where we'll create it:
添加类成员to记录VkImageView
for纹理image,创建新函数createTextureImageView
where我们创建它:
1 VkImageView textureImageView; 2 3 ... 4 5 void initVulkan() { 6 ... 7 createTextureImage(); 8 createTextureImageView(); 9 createVertexBuffer(); 10 ... 11 } 12 13 ... 14 15 void createTextureImageView() { 16 17 }
The code for this function can be based directly on createImageViews
. The only two changes you have to make are the format
and the image
:
这个函数的代码可以直接基于createImageViews
。仅有的2个改变是format
和image
:
VkImageViewCreateInfo viewInfo = {}; viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; viewInfo.image = textureImage; viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; viewInfo.format = VK_FORMAT_R8G8B8A8_UNORM; viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; viewInfo.subresourceRange.baseMipLevel = 0; viewInfo.subresourceRange.levelCount = 1; viewInfo.subresourceRange.baseArrayLayer = 0; viewInfo.subresourceRange.layerCount = 1;
I've left out the explicit viewInfo.components
initialization, because VK_COMPONENT_SWIZZLE_IDENTITY
is defined as 0
anyway. Finish creating the image view by calling vkCreateImageView
:
我省去了显式的viewInfo.components
初始化,因为VK_COMPONENT_SWIZZLE_IDENTITY
反正也是被定义为0
。完成创建image视图by调用vkCreateImageView
:
if (vkCreateImageView(device, &viewInfo, nullptr, &textureImageView) != VK_SUCCESS) { throw std::runtime_error("failed to create texture image view!"); }
Because so much of the logic is duplicated from createImageViews
, you may wish to abstract it into a new createImageView
function:
因为createImageViews
中有这么多冗余的逻辑,你可能希望抽象它到新函数createImageView
:
1 VkImageView createImageView(VkImage image, VkFormat format) { 2 VkImageViewCreateInfo viewInfo = {}; 3 viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; 4 viewInfo.image = image; 5 viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; 6 viewInfo.format = format; 7 viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; 8 viewInfo.subresourceRange.baseMipLevel = 0; 9 viewInfo.subresourceRange.levelCount = 1; 10 viewInfo.subresourceRange.baseArrayLayer = 0; 11 viewInfo.subresourceRange.layerCount = 1; 12 13 VkImageView imageView; 14 if (vkCreateImageView(device, &viewInfo, nullptr, &imageView) != VK_SUCCESS) { 15 throw std::runtime_error("failed to create texture image view!"); 16 } 17 18 return imageView; 19 }
The createTextureImageView
function can now be simplified to:
createTextureImageView
函数现在可以被简化为:
void createTextureImageView() { textureImageView = createImageView(textureImage, VK_FORMAT_R8G8B8A8_UNORM); }
And createImageViews
can be simplified to:
createImageViews
可以被简化为:
void createImageViews() { swapChainImageViews.resize(swapChainImages.size()); for (uint32_t i = 0; i < swapChainImages.size(); i++) { swapChainImageViews[i] = createImageView(swapChainImages[i], swapChainImageFormat); } }
Make sure to destroy the image view at the end of the program, right before destroying the image itself:
确保销毁image视图at程序结束时,在销毁image自己之前:
void cleanup() { cleanupSwapChain(); vkDestroyImageView(device, textureImageView, nullptr); vkDestroyImage(device, textureImage, nullptr); vkFreeMemory(device, textureImageMemory, nullptr);
Samplers 采样器
It is possible for shaders to read texels directly from images, but that is not very common when they are used as textures. Textures are usually accessed through samplers, which will apply filtering and transformations to compute the final color that is retrieved.
Shader有可能直接从image读取纹素,但是这不常见when它们用作纹理。纹理通常通过采样器来存取,which会实施过滤和变换to计算最后检索到的颜色。
These filters are helpful to deal with problems like oversampling. Consider a texture that is mapped to geometry with more fragments than texels. If you simply took the closest texel for the texture coordinate in each fragment, then you would get a result like the first image:
这些过滤器有助于解决问题-例如oversampling。考虑到一个纹理映射到几何体上,片段多于纹素的情况。如果你简单地采用纹理坐标上最接近的纹素in每个片段,那么你会得到一个下图左所示的情况:
If you combined the 4 closest texels through linear interpolation, then you would get a smoother result like the one on the right. Of course your application may have art style requirements that fit the left style more (think Minecraft), but the right is preferred in conventional graphics applications. A sampler object automatically applies this filtering for you when reading a color from the texture.
如果你结合4个最接近的纹素-通过线性插值,那么你会得到图右所示的更平滑的结果。当然你的app可能需要左边那种艺术效果(想想Minecraft),但是右边的在通常的图形app里是更需要的。一个采样器对象自动地为你实施这些过滤when从纹理读取颜色时。
Undersampling is the opposite problem, where you have more texels than fragments. This will lead to artifacts when sampling high frequency patterns like a checkerboard texture at a sharp angle:
Undersampling是个相反的问题,where你的纹素比Fragment多。这会导致锯齿when采样高频模式-如checkboard纹理at陡峭的角度:
As shown in the left image, the texture turns into a blurry mess in the distance. The solution to this is anisotropic filtering, which can also be applied automatically by a sampler.
如图左所示,纹理在远处呈现了模糊的混乱。解决方案是这个anisotropic filtering,它也会被采样器自动采用。
Aside from these filters, a sampler can also take care of transformations. It determines what happens when you try to read texels outside the image through its addressing mode. The image below displays some of the possibilities:
除了这些过滤器,采样器还可以处理变换问题。它决定了当你尝试读取image外部的纹素时会发生什么-通过它的取址模式。下图显示了可能的结果:
We will now create a function createTextureSampler
to set up such a sampler object. We'll be using that sampler to read colors from the texture in the shader later on.
我们现在要创建createTextureSampler
函数to设置这样一个采样器。我们要用这个采样器to稍后在shader中从纹理读取颜色。
void initVulkan() { ... createTextureImage(); createTextureImageView(); createTextureSampler(); ... } ... void createTextureSampler() { }
Samplers are configured through a VkSamplerCreateInfo
structure, which specifies all filters and transformations that it should apply.
采样器通过一个VkSamplerCreateInfo
结构体来配置,which指定了它应当实施的所有过滤和变换操作。
VkSamplerCreateInfo samplerInfo = {}; samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; samplerInfo.magFilter = VK_FILTER_LINEAR; samplerInfo.minFilter = VK_FILTER_LINEAR;
The magFilter
and minFilter
fields specify how to interpolate texels that are magnified or minified. Magnification concerns the oversampling problem describes above, and minification concerns undersampling. The choices are VK_FILTER_NEAREST
and VK_FILTER_LINEAR
, corresponding to the modes demonstrated in the images above.
和字段指定了放大的和缩小的纹素如何插值。放大关心的是上述oversampling问题,缩小关心的是undersampling问题。选项是VK_FILTER_NEAREST
和VK_FILTER_LINEAR
,对应于上图演示的模式。
samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
The addressing mode can be specified per axis using the addressMode
fields. The available values are listed below. Most of these are demonstrated in the image above. Note that the axes are called U, V and W instead of X, Y and Z. This is a convention for texture space coordinates.
取址模式可以逐轴指定-使用addressMode
字段。可用的值陈列如下。大多数都在上面的图中演示了。注意,轴被称为UVW而不是XYZ。这是纹理空间坐标的习惯。
VK_SAMPLER_ADDRESS_MODE_REPEAT
: Repeat the texture when going beyond the image dimensions. 当超过image范围时,重复纹理。VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT
: Like repeat, but inverts the coordinates to mirror the image when going beyond the dimensions. 像repeat,但是当超过image范围时,翻转坐标to镜像image。VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE
: Take the color of the edge closest to the coordinate beyond the image dimensions. 超过image范围时,使用最接近那个界限的边沿的颜色。VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE
: Like clamp to edge, but instead uses the edge opposite to the closest edge.像clamp to edge,但是使用最接近那个界限的边沿的对面的颜色。VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER
: Return a solid color when sampling beyond the dimensions of the image. 超过image范围时,使用某个固定的颜色。
It doesn't really matter which addressing mode we use here, because we're not going to sample outside of the image in this tutorial. However, the repeat mode is probably the most common mode, because it can be used to tile textures like floors and walls.
我们这里用什么取址模式并不重要,因为我们本教程中不会采样到image外部。但是,repeat模式可能是最常用的,因为它可以像地板和墙那样铺展纹理。
samplerInfo.anisotropyEnable = VK_TRUE; samplerInfo.maxAnisotropy = 16;
These two fields specify if anisotropic filtering should be used. There is no reason not to use this unless performance is a concern. The maxAnisotropy
field limits the amount of texel samples that can be used to calculate the final color. A lower value results in better performance, but lower quality results. There is no graphics hardware available today that will use more than 16 samples, because the difference is negligible beyond that point.
这2个字段指定是否使用各向异性过滤。除非处于性能考虑,否则没有理由不使用它。maxAnisotropy
字段限制了可用于计算最后颜色的纹素采样的数量。现在没有一个图形硬件支持超过16个采样,因为超过16的区别就微不足道了。
samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
The borderColor
field specifies which color is returned when sampling beyond the image with clamp to border addressing mode. It is possible to return black, white or transparent in either float or int formats. You cannot specify an arbitrary color.
borderColor
字段指定了当采样位置超过image且使用clamp to border取址模式时,返回什么颜色。可以返回黑色、白色或透明色in浮点数或整数格式。你不能指定任意的颜色。
samplerInfo.unnormalizedCoordinates = VK_FALSE;
The unnormalizedCoordinates
field specifies which coordinate system you want to use to address texels in an image. If this field is VK_TRUE
, then you can simply use coordinates within the [0, texWidth)
and [0, texHeight)
range. If it is VK_FALSE
, then the texels are addressed using the [0, 1)
range on all axes. Real-world applications almost always use normalized coordinates, because then it's possible to use textures of varying resolutions with the exact same coordinates.
unnormalizedCoordinates
字段指定了你想用哪个坐标系统来在image中取址。如果字段为VK_TRUE
,那么你可以简单地使用[0, texWidth)
和[0, texHeight)
范围的坐标。如果字段为VK_FALSE
,那么在所有坐标轴上使用[0, 1)
范围进行纹素的取址。真实的应用程序几乎总数使用标准坐标,因为这样就可以使用不同解析度的纹理with完全相同的坐标。
samplerInfo.compareEnable = VK_FALSE;
samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
If a comparison function is enabled, then texels will first be compared to a value, and the result of that comparison is used in filtering operations. This is mainly used for percentage-closer filtering on shadow maps. We'll look at this in a future chapter.
如果启用比较函数,那么纹素会先与一个值比较,比较结果用于过滤操作。这注意用于阴影映射的percentage-closer filtering。我们将在后续章节讨论它。
samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; samplerInfo.mipLodBias = 0.0f; samplerInfo.minLod = 0.0f; samplerInfo.maxLod = 0.0f;
All of these fields apply to mipmapping. We will look at mipmapping in a later chapter, but basically it's another type of filter that can be applied.
所有这些字段都应用到mipmap上。我们将在later chapter细说mipmap,但基本上它是可以应用的另一种过滤。
The functioning of the sampler is now fully defined. Add a class member to hold the handle of the sampler object and create the sampler with vkCreateSampler
:
采样器的功能现在完全被定义好了。添加类成员to记录这个采样器对象的句柄,用vkCreateSampler
创建采样器:
VkImageView textureImageView; VkSampler textureSampler; ... void createTextureSampler() { ... if (vkCreateSampler(device, &samplerInfo, nullptr, &textureSampler) != VK_SUCCESS) { throw std::runtime_error("failed to create texture sampler!"); } }
Note the sampler does not reference a VkImage
anywhere. The sampler is a distinct object that provides an interface to extract colors from a texture. It can be applied to any image you want, whether it is 1D, 2D or 3D. This is different from many older APIs, which combined texture images and filtering into a single state.
注意,采样器不在任何地方引用VkImage
。采样器是独立的对象that提供接口to从纹理中提取颜色。它可被用于任何你想要的image上,无论它是1D、2D还是3D的。这与很多旧的API不同,which联合image和过滤器到一个单独的状态。
Destroy the sampler at the end of the program when we'll no longer be accessing the image:
当我们不再存取image时,在程序的最后销毁采样器:
void cleanup() { cleanupSwapChain(); vkDestroySampler(device, textureSampler, nullptr); vkDestroyImageView(device, textureImageView, nullptr); ... }
Anisotropy device feature 各向异性设备特性
If you run your program right now, you'll see a validation layer message like this:
如果你现在运行程序,你会看到验证层消息如下:
That's because anisotropic filtering is actually an optional device feature. We need to update the createLogicalDevice
function to request it:
这是因为各向一些过滤实际上是可选的设备特性。我们需要更新createLogicalDevice
函数to请求它:
VkPhysicalDeviceFeatures deviceFeatures = {};
deviceFeatures.samplerAnisotropy = VK_TRUE;
And even though it is very unlikely that a modern graphics card will not support it, we should update isDeviceSuitable
to check if it is available:
即使现代图形卡不太可能不支持它,我们也应当更新isDeviceSuitable
to检查它是否可用:
bool isDeviceSuitable(VkPhysicalDevice device) { ... VkPhysicalDeviceFeatures supportedFeatures; vkGetPhysicalDeviceFeatures(device, &supportedFeatures); return indices.isComplete() && extensionsSupported && swapChainAdequate && supportedFeatures.samplerAnisotropy; }
The vkGetPhysicalDeviceFeatures
repurposes the VkPhysicalDeviceFeatures
struct to indicate which features are supported rather than requested by setting the boolean values.
vkGetPhysicalDeviceFeatures
改换VkPhysicalDeviceFeatures
结构体的意图to表明支持哪些特性,而不是请求哪些特性by设置boolean值。
Instead of enforcing the availability of anisotropic filtering, it's also possible to simply not use it by conditionally setting:
不是强制各向异性过滤的可用性,而是可以简单地根据条件来不用它:
samplerInfo.anisotropyEnable = VK_FALSE; samplerInfo.maxAnisotropy = 1;
In the next chapter we will expose the image and sampler objects to the shaders to draw the texture onto the square.
下一章我们将image和采样器暴露给shader,在四边形上绘制纹理。
C++ code / Vertex shader / Fragment shader
![]() |
微信扫码,自愿捐赠。天涯同道,共谱新篇。
微信捐赠不显示捐赠者个人信息,如需要,请注明联系方式。 |