版本记录
版本号 | 时间 |
---|---|
V1.0 | 2018.10.08 星期一 |
前言
很多做视频和图像的,相信对这个框架都不是很陌生,它渲染高级3D图形,并使用GPU执行数据并行计算。接下来的几篇我们就详细的解析这个框架。感兴趣的看下面几篇文章。
1. Metal框架详细解析(一)—— 基本概览
2. Metal框架详细解析(二) —— 器件和命令(一)
3. Metal框架详细解析(三) —— 渲染简单的2D三角形(一)
4. Metal框架详细解析(四) —— 关于GPU Family 4(一)
5. Metal框架详细解析(五) —— 关于GPU Family 4之关于Imageblocks(二)
6. Metal框架详细解析(六) —— 关于GPU Family 4之关于Tile Shading(三)
7. Metal框架详细解析(七) —— 关于GPU Family 4之关于光栅顺序组(四)
8. Metal框架详细解析(八) —— 关于GPU Family 4之关于增强的MSAA和Imageblock采样覆盖控制(五)
9. Metal框架详细解析(九) —— 关于GPU Family 4之关于线程组共享(六)
10. Metal框架详细解析(十) —— 基本组件(一)
11. Metal框架详细解析(十一) —— 基本组件之器件选择 - 图形渲染的器件选择(二)
12. Metal框架详细解析(十二) —— 基本组件之器件选择 - 计算处理的设备选择(三)
13. Metal框架详细解析(十三) —— 计算处理(一)
14. Metal框架详细解析(十四) —— 计算处理之你好,计算(二)
15. Metal框架详细解析(十五) —— 计算处理之关于线程和线程组(三)
16. Metal框架详细解析(十六) —— 计算处理之计算线程组和网格大小(四)
17. Metal框架详细解析(十七) —— 工具、分析和调试(一)
18. Metal框架详细解析(十八) —— 工具、分析和调试之Metal GPU Capture(二)
19. Metal框架详细解析(十九) —— 工具、分析和调试之GPU活动监视器(三)
20. Metal框架详细解析(二十) —— 工具、分析和调试之关于Metal着色语言文件名扩展名、使用Metal的命令行工具构建库和标记Metal对象和命令(四)
21. Metal框架详细解析(二十一) —— 基本课程之基本缓冲区(一)
Basic Texturing - 基本纹理
演示如何加载图像数据和纹理四边形。
在Basic Buffers示例中,您学习了如何在Metal中渲染基本几何体。
在此示例中,您将学习如何通过将纹理应用于单个四边形来渲染2D图像。 特别是,您将学习如何配置纹理属性,解释纹理坐标以及访问片段函数中的纹理。
Images and Textures - 图像和纹理
任何图形技术的一个关键特性是处理和绘制图像的能力。 Metal以包含图像数据的纹理形式支持此功能。 与常规2D图像不同,纹理可以以更具创造性的方式使用,并应用于更多表面类型。 例如,纹理可用于替换选定的顶点位置,或者它们可以完全包裹在3D对象周围。 在此示例中,图像数据被加载到纹理中,应用于单个四边形,并呈现为2D图像。
Load Image Data - 加载图像数据
Metal
框架不提供直接将文件中的图像数据加载到纹理的API。 相反,Metal应用程序或游戏依赖于自定义代码或其他框架(如Image I / O,MetalKit,UIKit
或AppKit
)来处理图像文件。 Metal本身仅分配纹理资源,然后使用先前加载到内存中的图像数据填充它们。
在此示例中,为简单起见,自定义AAPLImage
类将图像数据从文件(Image.tga)
加载到内存(NSData)
中。
注意:
AAPLImage
类不是此示例的焦点,因此不对其进行详细讨论。 该类演示了基本的图像加载操作,但不以任何方式使用或依赖于Metal框架。 其唯一目的是便于加载该特定示例的图像数据。
此示例使用TGA
文件格式以简化。 该文件由描述元数据的标题(例如图像尺寸)和图像数据本身组成。 这种文件格式的关键点是图像数据的存储器布局;特别是每个像素的布局。
Metal要求使用特定的MTLPixelFormat
值格式化所有纹理。 像素格式描述了每个纹理像素(其纹理像素)的布局。 要使用图像数据填充Metal纹理,其像素数据必须已经采用Metal
兼容的像素格式进行格式化,该格式由单个MTLPixelFormat
枚举值定义。 此示例使用MTLPixelFormatBGRA8Unorm
像素格式,表示每个像素具有以下内存布局:
此像素格式使用每像素32位,按照蓝色,绿色,红色和alpha顺序排列为每个组件8位。 每个像素使用32位的TGA
文件已经以这种格式排列,因此不需要进一步的转换操作。 但是,此示例使用每像素24位BGR
图像,需要为每个像素添加额外的8位alpha分量。 由于alpha通常定义图像的不透明度,并且样本的图像完全不透明,因此32位BGRA像素的附加8位alpha分量设置为255。
在AAPLImage
类加载图像文件之后,可以通过对data
属性的查询来访问图像数据。
// Initialize a source pointer with the source image data that's in BGR form
uint8_t *srcImageData = ((uint8_t*)fileData.bytes +
sizeof(TGAHeader) +
tgaInfo->IDSize);
// Initialize a destination pointer to which you'll store the converted BGRA
// image data
uint8_t *dstImageData = mutableData.mutableBytes;
// For every row of the image
for(NSUInteger y = 0; y < _height; y++)
{
// For every column of the current row
for(NSUInteger x = 0; x < _width; x++)
{
// Calculate the index for the first byte of the pixel you're
// converting in both the source and destination images
NSUInteger srcPixelIndex = 3 * (y * _width + x);
NSUInteger dstPixelIndex = 4 * (y * _width + x);
// Copy BGR channels from the source to the destination
// Set the alpha channel of the destination pixel to 255
dstImageData[dstPixelIndex + 0] = srcImageData[srcPixelIndex + 0];
dstImageData[dstPixelIndex + 1] = srcImageData[srcPixelIndex + 1];
dstImageData[dstPixelIndex + 2] = srcImageData[srcPixelIndex + 2];
dstImageData[dstPixelIndex + 3] = 255;
}
}
_data = mutableData;
Create a Texture - 创建纹理
MTLTextureDescriptor
对象用于配置MTLTexture
对象的纹理尺寸和像素格式等属性。 然后调用newTextureWithDescriptor:
方法创建一个空纹理容器并为纹理数据分配足够的内存。
MTLTextureDescriptor *textureDescriptor = [[MTLTextureDescriptor alloc] init];
// Indicate that each pixel has a blue, green, red, and alpha channel, where each channel is
// an 8-bit unsigned normalized value (i.e. 0 maps to 0.0 and 255 maps to 1.0)
textureDescriptor.pixelFormat = MTLPixelFormatBGRA8Unorm;
// Set the pixel dimensions of the texture
textureDescriptor.width = image.width;
textureDescriptor.height = image.height;
// Create the texture from the device by using the descriptor
_texture = [_device newTextureWithDescriptor:textureDescriptor];
与存储多种自定义数据的MTLBuffer
对象不同,MTLTexture
对象专门用于存储格式化的图像数据。 尽管MTLTextureDescriptor
对象指定了足够的信息来分配纹理内存,但还需要其他信息来填充空纹理容器。 通过replaceRegion:mipmapLevel:withBytes:bytesPerRow:
方法用MTLTexture
对象填充图像数据。
图像数据通常按行组织。 此示例计算每行的字节数,即每个像素的字节数乘以图像宽度。 这种类型的图像数据被认为是紧密打包(tightly packed)
的,因为后续像素行的数据紧跟在前一行之后。
NSUInteger bytesPerRow = 4 * image.width;
纹理具有已知的尺寸,可以解释为像素区域。 MTLRegion
结构用于标识纹理的特定区域。 此示例使用图像数据填充整个纹理;因此,覆盖整个纹理的像素区域等于纹理的尺寸。
MTLRegion region = {
{ 0, 0, 0 }, // MTLOrigin
{image.width, image.height, 1} // MTLSize
};
注意:要指定纹理的子区域,
MTLRegion
结构必须具有非零原点值或任何纹理尺寸的较小尺寸值。
每行和特定像素区域的字节数是用于使用图像数据填充空纹理容器的必需参数。 调用replaceRegion:mipmapLevel:withBytes:bytesPerRow:
方法通过将image.data.bytes
指针中的数据复制到_texture
对象来执行此操作。
[_texture replaceRegion:region
mipmapLevel:0
withBytes:image.data.bytes
bytesPerRow:bytesPerRow];
Texture Coordinates - 纹理坐标
片段函数的主要任务是处理传入的片段数据并计算可绘制像素的颜色值。此示例的目标是通过将纹理应用于单个四边形来显示屏幕上每个纹素的颜色。因此,样本的片段函数必须能够读取每个纹素并输出其颜色。
纹理不能单独渲染;它必须对应于顶点函数输出的一些几何表面,并由光栅化器变成碎片。此关系由纹理坐标定义:浮点位置,将纹理图像上的位置映射到几何表面上的位置。
对于2D纹理,纹理坐标在x和y方向上的值均为0.0到1.0。值(0.0,0.0)
映射到图像数据的第一个字节(图像的左下角)处的纹素。值(1.0,1.0)
映射到图像数据的最后一个字节(图像的右上角)处的纹素。遵循这些规则,访问图像中心的纹素需要指定纹理坐标(0.5,0.5)
。
Map the Vertex Texture Coordinates - 映射顶点纹理坐标
要渲染完整的2D图像,必须将包含图像数据的纹理映射到定义2D四边形的顶点上。 在此示例中,每个四边形的顶点指定一个纹理坐标,将四边形的角映射到纹理的角。
static const AAPLVertex quadVertices[] =
{
// Pixel positions, Texture coordinates
{ { 250, -250 }, { 1.f, 0.f } },
{ { -250, -250 }, { 0.f, 0.f } },
{ { -250, 250 }, { 0.f, 1.f } },
{ { 250, -250 }, { 1.f, 0.f } },
{ { -250, 250 }, { 0.f, 1.f } },
{ { 250, 250 }, { 1.f, 1.f } },
};
vertexShader
顶点函数通过将这些值写入RasterizerData
输出结构的textureCoordinate
成员中,沿管道传递这些值。 这些值在四边形的三角形片段中进行插值,类似于Hello Triangle样本中的插值颜色值。
out.textureCoordinate = vertexArray[vertexID].textureCoordinate;
Sample Texels - 示例特征
samplingShader
片段函数的签名包括colorTexture
参数,该参数具有texture2d
类型并使用[[texture(index)]]
属性限定符。 此参数是对MTLTexture
对象的引用,用于读取其纹素。
fragment float4
samplingShader(RasterizerData in [[stage_in]],
texture2d<half> colorTexture [[ texture(AAPLTextureIndexBaseColor) ]])
读取纹素也称为采样。 片段函数使用内置纹理sample()
函数来对texel
数据进行采样。 sample()
函数有两个参数:一个采样器(textureSampler)
和一个纹理坐标(in.textureCoordinate)
。 采样器用于计算纹理元素的颜色,纹理坐标用于定位特定纹理元素。
当渲染的区域与纹理的大小不同时,采样器可以使用不同的算法来精确计算sample()
函数应返回的texel
颜色。 mag_filter
模式指定当区域大于纹理大小时,采样器应如何计算返回的颜色;min_filter
模式指定当区域小于纹理大小时,采样器应如何计算返回的颜色。 为两个滤镜设置线性linear
模式可使采样器平均给定纹理坐标周围的纹素颜色,从而使输出图像更平滑。
constexpr sampler textureSampler (mag_filter::linear,
min_filter::linear);
// Sample the texture to obtain a color
const half4 colorSample = colorTexture.sample(textureSampler, in.textureCoordinate);
Set a Fragment Texture - 设置片段纹理
此示例使用AAPLTextureIndexBaseColor
索引来标识Objective-C
和Metal Shading Language
代码中的纹理。 片段函数也采用与顶点函数类似的参数,你可以调用setFragmentTexture:atIndex:
方法在特定索引处设置纹理。
[renderEncoder setFragmentTexture:_texture
atIndex:AAPLTextureIndexBaseColor];
在此示例中,您学习了如何通过将纹理应用于单个四边形来渲染2D图像。
后记
本篇主要讲述了基本纹理,感兴趣的给个赞或者关注~~~