创建和采样纹理
将图像数据加载到纹理中并将其应用于四边形
概述
您可以使用纹理在metal中绘制和处理图像。纹理是纹理元素,通常被称为MTKTextureLoader的结构化集合纹元或像素。这些纹理元素的确切配置取决于纹理的类型。此样本使用构造为2D元素数组的纹理来保存图像,每个元素均包含颜色数据。通过称为纹理映射的过程将纹理绘制到几何图元上。片段功能通过对纹理进行采样来为每个片段生成颜色。
纹理由MTLTextTure对象管理。一个MTLTextture对象定义了纹理的格式,包括元素的大小和布局,纹理中的元素的数量以及这些元素的组织方式。创建后,纹理的格式和组织就永远不会改变。但是,可以通过渲染纹理或将数据复制到其中来更改纹理的内容。
Metal 框架没有提供直接将图像数据从文件加载到纹理的api,metal本身仅分配纹理资源,并提供在纹理之间来回复制数据的方法。metal应用程序依靠自定义代码或其他框架(例如MetalKit,imageI/0,UIKit或Appkit)来处理图像文件。例如,您可以用来执行简单的纹理加载,
此示例说明如何编写自定义纹理加载器。
Xcode 项目包含用于在 macOS,iOS 或 tvOS 设备上运行示例的方案,默认方案是macos,它将按mac上的方式运行示例。
加载和格式化图像数据
您可以手动创建纹理或更新其内容,接下来的几节将介绍该过程,您可能出于多种原因执行此操作:
-
您具有以自定义格式存储的图像数据
-
您具有需要在运行时生成其内容的纹理。
-
您正在从服务器流式传输纹理数据,否则需要动态更新纹理的内容。
在示例中,AAPLImage 该类从TGA文件加载并解析图像数据,该类将来自TGA文件的像素数据转换为Metal可以理解的像素格式。该示例使用图像的元素数据创建新的metal纹理并将像素数据复制到该纹理中。
该AAPLImage 课程不是本示例的重点,因此不会对其进行详细讨论。该类演示基本的图像加载操作,但不使用也不依赖于metal框架,其唯一目的是方便加载图像数据并将其转换为金属像素格式,如果您需要加载自定义格式的图像,则可以创建一个类似的类。
金属要求将所有纹理格式化为MTLPixelFormat特定值,像素格式描述纹理中像素数据的布局。此示例使用MTLpixelFormatBGRA8Unorm像素格式,该格式每个像素使用32位,每个分量按蓝色,绿色,红色和alpha顺序排列位8位
01060313-c1d0-4aa7-9038-9e34e9e77187.png必须先将图像数据格式化为纹理的像素格式,然后才能填充“金属”纹理。TGA文件可以按32位/像素格式或24位/像素格式提供像素数据。已经使用这种格式排列了每个像素使用32位的TGA文件,因此您只需复制像素数据即可。要转换每像素24位的BGR图像,请复制红色,绿色和蓝色通道,并将alpha 通道设置为255,表示完全不透明的像素。
uint8_t *srcimageData = ((uint8_t *)fileData.bytes + sizeof(TGAHeader) + tgaInfo->IDSize);
uint8_ t *dstImageData = mutableData.mutableBytes;
for (nsuinteger y = 0; y < _height; y++) {
nsuinteger srcRow = (tgaInfo -> topOrigin) ? y : _height - 1 - y;
for (nsuinteger x = 0; x < _width;x++) {
nsuinteger srcColumn = (tgaInfo-> rightOrigin) ? _width - 1- x: x;
nsuinteger srcPixelIndex = srcBytersPerPixel * (srcRow * _width + srcColumn);
nsuinteger dstPixelindex = 4 * (y * _width + x);
dstImageData[dstPixelIndex + 0] = srcImageData[srcPixelIndex + 0];
dstImageData[dstPixelIndex + 1] = srcImageData[srcPixelIndex + 1];
dstimageData[dstPixelIndex + 2] = srcImageData[srcPixelIndex + 2];
if (tgaInfo-> bitsPerPixel) == 32) {
dstImageData[dstPixelIndex + 3] = srcImageData[srcPixelIndex + 3];
}
else {
dstImageData[dstPixelIndex + 3] = 255;
}
}
}
_data = mutableData;
从纹理描述符创建纹理
使用MTLTextureDescriptor对象来配置属性,例如对象的纹理尺寸和像素格式,然后调用MTLTexturewithDescritpor该方法来创建纹理。
MTLTextureDescriptor *textureDescriptor = [[MTLTextureDescritpor alloc] init];
textureDescriptor.pixelFormat = MTLPixelFormatBGRA8Unorm;
textureDescriptor.width = image.width;
textureDescriptor.height = image.height;
id<MTLTexture> texture = [_device newTextureWithDescriptor:textureDescritpor];
Metal 创建一个MTLTexture对象并为纹理数据分配内存,创建纹理时,该内存未初始化,因此下一步是将数据复制到纹理中。
将图像数据复制到纹理中
金属管理纹理的内存,并且不提供直接访问它的权限,因此,您将无法获得指向内存中纹理数据的指针并自行复制像素,相反,您可以在MTLTexture对象上条用方法以从内存中复制数据,然后可以将其访问到纹理中,反之亦然。
在此示例中,AAPLImage对象为图像数据分配了内存,因此您将告诉纹理对象复制该数据。
使用MTLRegion结构来标识要更新纹理的哪一部分。该示例使用图像数据填充整个纹理。因此,创建一个覆盖整个纹理的区域。
MTLRegion region = {
{0,0,0},
{image.width,image.height,1}
}
图像数据通常是按行组织的,您需要告诉metal 在源图像中行之间的偏移量。图像加载代码以紧密打包的格式创建图像数据,因此后续像素行的数据紧随前一行,将行之间的便宜量计算为一行的确切长度(以字节为单位),即每像素的字节数乘以图像宽度.
nsuinteger bytesPerRow = 4 * image.width;
AAPLImage在纹理上调用replaceRegion:mipmapLevel:widthBytes:bytesPerRow:方法以将像素数据从对象复制到纹理中。
[texture replaceRegion:region mimapLevel:0 withBytes:image.data.bytes bytesPerRow:bytesPerRow];
将纹理映射到几何图元上
您不能单独渲染纹理,您必须将其映射到几何原语(在此示例中为一对三角形),这些原语由顶点阶段输出并有光栅化器转换为片段。每个片段都需要知道应该将纹理的哪一部分应用到它,您可以使用纹理坐标定义此映射:将映射图像上的位置映射到几何表面上的位置的浮点位置。
对于2D纹理,归一化纹理坐标在x和y方向上均为0.0到1.0之间到值。值(0.0,0.0)在纹理数据的第一个字节(图像的左上角)指定纹理像素。值(1.0,1.0)在纹理数据的最后一个字节(图像的右下角)指定纹理像素
rendered2x-1610753212.png将一个字段添加到顶点格式以保存纹理坐标
typedef struct {
vector_float2 position;
vector_float2 textureCoordinate;
} AAPLVertex;
在顶点数据中,将四边形的角映射到纹理的角
static const AAPLVertex quadVertices[] = {
{{250,-250},{1.0,1.0}},
{{-250,-250},{.0,1.0}},
{{-250,250},{.0,.0}},
{{250,-250},{1.0,1.0}},
{{-250,250},{.0,1.0}},
{{250,250},{1.0,.0}},
}
要将纹理坐标发送到片段着色器,请在RasterizerData数据结构中添加textureCoordinate一个值
struct RasterizerData
{
float4 position[[position]];
float2 textureCoordinate;
}
在顶点着色器中,通过将纹理坐标写入字段将其传递到光栅化器阶段,光栅化程序阶段将这些坐标插值到四边形的三角形片段中。
out.texureCoordinate = vertexArray[vertexID].textureCoordinate;
从纹理中的位置计算颜色
您对纹理进行采样,以根据纹理中的位置计算颜色。要对纹理数据进行采样,fragment函数需要纹理坐标和对要采样的纹理的引用。除了从光栅化器阶段传递的参数外,还要传递具有texture2d类型和[[texture(index)]]属性限定符的参数。此参数是对要采样的MTLTexture对象的应用。
fragment float4
samplingShader(RasterizerData in [[stage_in]],
texture2d<half> colorTexture [[ texture(AAPLTextureIndexBaseColor)]])
使用内置的纹理sample()功能来采样纹理像素数据。该sample()函数有两个参数:一个用于描述如何对纹理进行采样的采样器textureSampler和一个用于描述要采样的纹理位置的纹理坐标(.textureCoordinate),该函数从纹理中提取一个或多个像素,并返回根据这些像素计算出的颜色。
当要渲染的区域与纹理大小不同时,采样器可以使用不同的算法来精确计算sample()函数应返回的纹理颜色。设置模式以指定当面积大于纹理的大小时采样器应如何计算返回的颜色,以及设置模式以指定面积小于纹理的大小时采样器应如何计算返回的颜色。mag_filter::linear,min_filter::linear为两个滤镜设置模式可使采样器平均给定纹理坐标周围像素的颜色,从而使输出图像更平滑。
constexpr sampler textureSampler(mag_filter::linear, min_filter::linear);
const. half4 colorSample = colorTexture.sample(textureSampler,in.textureCoordinate);
尝试增大或减小四边形的大小,以查看过滤的工作原理
编码绘制参数
编码和提交绘图命令的过程与使用渲染管道渲染基元中显示的过程相同,因此下面没有显示完整的代码。此样本中的区别在于片段着色器具有附加参数。对命令的参数进行编码时,请设置片段函数的texture参数,此示例AAPLTextureIndexBaseColor使用索引来识别Objective-c 和 Metal shading Language 代码中的纹理。
[renderEncoder setFragmentTexture:_texture atIndex:AAPLTextureIndexBaseColor];
网友评论