Metal 框架之创建纹理及纹理采样
概述
Metal 中使用纹理来绘制和处理图像,它是由像素组成的。使用2维数组的纹理来保存图像,每个元素都包含颜色数据。通过纹理映射技术将纹理绘制到几何图元上。 在片段着色器中,使用片段函数对纹理采样来为每个片段生成颜色。
Metal 中,使用 MTLTexture 对象来表示纹理。 MTLTexture 对象定义了纹理的格式,包括大小和布局、纹理中元素的数量以及这些元素的组织方式。 一旦创建了纹理,它的格式和组织方式就固定不变了,后续只能通过渲染或将数据复制到其中来更改纹理的内容。
Metal 框架没有提供将图像数据从文件加载到生成纹理的功能,它只分配纹理资源,并提供将数据复制到纹理及从纹理复制数据的方法。 因此,只能自己写代码使用其他框架(如 MetalKit、Image I/O、UIKit 或 AppKit)来处理图像文件。在 Metal 中,可以使用 MTKTextureLoader 来加载纹理。 本文将展示如何编写自定义纹理加载器。
加载图像资源
出于一下原因,需要手动创建或更新纹理。
自定义格式的图片数据
需要运行时创建的纹理
从服务器传过来的纹理数据或者需要动态更新的纹理内容
Metal 能够加载的纹理需要是 MTLPixelFormat 类型的数据。 像素格式描述了像素数据在纹理中的布局。 本例使用 MTLPixelFormatBGRA8Unorm 像素格式,每像素占 32 位,按照蓝色、绿色、红色和 alpha 顺序来组织,每个部分占8位:
在填充 Metal 纹理之前,必须将图像数据格式化为纹理的像素格式。 TGA 文件可以提供每像素 32 位格式或每像素 24 位格式的像素数据。 对于每像素 32 位的 TGA 文件,在使用时只需要拷贝像素数据就可以了。 对于每像素 24 位的 BGR 图像,使用时需要做转换处理,复制红色、绿色和蓝色通道并将 alpha 通道设置为 255即可。
```
// Initialize a source pointer with the source image data that's in BGR form
uint8t *srcImageData = ((uint8t*)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++)
{
// If bit 5 of the descriptor is not set, flip vertically
// to transform the data to Metal's top-left texture origin
NSUInteger srcRow = (tgaInfo->topOrigin) ? y : _height - 1 - y;
// For every column of the current row
for(NSUInteger x = 0; x < _width; x++)
{
// If bit 4 of the descriptor is set, flip horizontally
// to transform the data to Metal's top-left texture origin
NSUInteger srcColumn = (tgaInfo->rightOrigin) ? _width - 1 - x : x;
// Calculate the index for the first byte of the pixel you're
// converting in both the source and destination images
NSUInteger srcPixelIndex = srcBytesPerPixel * (srcRow * _width + srcColumn);
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];
if(tgaInfo->bitsPerPixel == 32)
{
dstImageData[dstPixelIndex + 3] = srcImageData[srcPixelIndex + 3];
}
else
{
dstImageData[dstPixelIndex + 3] = 255;
}
}
}
_data = mutableData;
```
从纹理描述符创建纹理
使用 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
id texture = [_device newTextureWithDescriptor:textureDescriptor];
```
使用 Metal 创建一个 MTLTexture 对象并为纹理数据分配内存,此内存并未初始化,因此下一步需要将数据拷贝到纹理中。
```
MTLRegion region = {
{ 0, 0, 0 }, // MTLOrigin
{image.width, image.height, 1} // MTLSize
};
```
图像数据通常按行来存储,在使用图像时需要告诉 Metal 源图像中行之间的偏移量。 本例中图像数据是按照紧密的格式存储的,因此各行像素之间是紧挨着的。
```
NSUInteger bytesPerRow = 4 * image.width;
```
调用纹理上的 replaceRegion:mipmapLevel:withBytes:bytesPerRow: 方法将像素数据从图像对象复制到纹理中。
```
[texture replaceRegion:region
mipmapLevel:0
withBytes:image.data.bytes
bytesPerRow:bytesPerRow];
```
将纹理映射到几何图元
纹理不能被直接渲染,必须将其映射到几何图元上才可以。这些几何图元由顶点阶段输出并由光栅化器转换为片段的几何图元(在本例中为一对三角形)。 每个片段都需要知道纹理的哪一部分作用到它上面,可以使用纹理坐标定义此映射:将纹理图像上的位置映射到几何表面上的位置。
对于 2D 纹理,采用归一化之后的纹理坐标, 在 x 轴和 y 轴方向上都是从 0.0 到 1.0。 (0.0, 0.0) 指定纹理数据的第一个字节(图像的左上角)处的纹素。 (1.0, 1.0) 指定纹理数据最后一个字节(图像的右下角)的纹素。
定义一种数据结构来保存顶点数据与纹理坐标:
```
typedef struct
{
// Positions in pixel space. A value of 100 indicates 100 pixels from the origin/center.
vector_float2 position;
// 2D texture coordinate
vector_float2 textureCoordinate;
} AAPLVertex;
```
在顶点数据中,将四边形的四个角映射到纹理的四个角上:
```
static const AAPLVertex quadVertices[] =
{
// Pixel positions, Texture coordinates
{ { 250, -250 }, { 1.f, 1.f } },
{ { -250, -250 }, { 0.f, 1.f } },
{ { -250, 250 }, { 0.f, 0.f } },
{ { 250, -250 }, { 1.f, 1.f } },
{ { -250, 250 }, { 0.f, 0.f } },
{ { 250, 250 }, { 1.f, 0.f } },
};
```
定义 RasterizerData 数据结构来存储纹理坐标 textureCoordinate 值,该值后续将被传进片段着色器中:
```
struct RasterizerData
{
// The [[position]] attribute qualifier of this member indicates this value is
// the clip space position of the vertex when this structure is returned from
// the vertex shader
float4 position [[position]];
// Since this member does not have a special attribute qualifier, the rasterizer
// will interpolate its value with values of other vertices making up the triangle
// and pass that interpolated value to the fragment shader for each fragment in
// that triangle.
float2 textureCoordinate;
};
```
在顶点着色器中,需要将纹理坐标写入 textureCoordinate 字段中,才能将纹理坐标传递给光栅化阶段。 光栅化阶段会在四边形的三角形片段中插入一些坐标点来产生屏幕上的像素。
```
out.textureCoordinate = vertexArray[vertexID].textureCoordinate;
```
计算顶点颜色
通过纹理采样来计算某个位置点的颜色。具体颜色的计算依赖于片段函数,它需要根据纹理坐标以及纹理来采样纹理数据得到颜色值。 除了从光栅化阶段传入的参数之外,还传入一个 texture2d 类型的 colorTexture 参数,该参数指向 MTLTexture 对象。
```
fragment float4
samplingShader(RasterizerData in [[stage_in]],
texture2d colorTexture [[ texture(AAPLTextureIndexBaseColor) ]])
```
使用内置的纹理 sample() 函数对纹素数据进行采样。 sample() 函数接受两个参数:一个采样器 (textureSampler) 描述您想要如何对纹理进行采样,以及描述纹理中要采样的位置的纹理坐标 (in.textureCoordinate)。 sample() 函数从纹理中获取一个或多个像素,并返回根据这些像素计算出的颜色。
当渲染到的区域与纹理的大小不同时,采样器可以使用不同的算法来准确计算 sample() 函数应该返回的纹素颜色。 设置magfilter模式指定区域大于纹理大小时采样器如何计算返回颜色,设置minfilter模式指定区域小于纹理大小时采样器如何计算返回颜色。 为两个过滤器设置线性模式会使采样器平均给定纹理坐标周围像素的颜色,从而产生更平滑的输出图像。
``` constexpr sampler textureSampler (mag_filter::linear,
min_filter::linear);
// Sample the texture to obtain a color
const half4 colorSample = colorTexture.sample(textureSampler, in.textureCoordinate)
```
设置绘图参数
编码和提交绘图命令的过程与使用渲染管道渲染基元中所示的过程相同。当对命令的参数进行编码时,需要设置片段函数的纹理参数,本例使用 AAPLTextureIndexBaseColor 索引来标识 Objective-C 和 Metal Shading Language 代码中的纹理。
```
[renderEncoder setFragmentTexture:_texture
atIndex:AAPLTextureIndexBaseColor];
```
总结
本文介绍了 Metal 中使用纹理来绘制和处理图像的步骤。一个纹理先要通过纹理映射技术将纹理映射到几何图元上后才可以被渲染出来。
首先是加载图像数据,转换成 MTLTexture 对象的纹理,然后进行纹理映射、设置滤波方式,最后采样计算颜色值,最后编码和提交绘图命令。
Metal 框架之创建纹理及纹理采样相关推荐
- Metal 框架之从可绘制纹理中读取像素数据
概述 Metal 优化了纹理以供 GPU 快速访问,但不允许直接从 CPU 访问纹理的内容.当 App 需要更改或读取纹理的内容时,需要 Metal 在纹理和可访问的 CPU 内存(系统内存或使用共享 ...
- Metal 框架之自定义设置渲染通道
概述 渲染通道是一系列渲染命令,用于绘制一组纹理.本示例执行一对渲染通道来渲染视图的内容.对于第一个通道,示例创建了一个自定义渲染,将图像渲染成纹理.这个通道是一个离屏渲染通道,因为样本渲染为普通纹理 ...
- Metal 框架之资源存储模式
概述 Metal 中使用 MTLStorageMode 来指定资源的内存位置和访问权限. MTLStorageMode 是个枚举类型,定义如下: public enum MTLStorageMode ...
- 基于 Metal 框架的 GPU 计算
概述 GPU 的优势在于并发计算能力,在本示例中,你将学习如何使用 Apple 的新框架 Metal 来实现并发计算. 你将学会如何将用C编写的简单函数转换为 Metal Shading Langua ...
- 纹理对象纹理单元纹理目标_网页设计理论:纹理
纹理对象纹理单元纹理目标 Texture has become an indispensable element in web design. It is not only a trend but a ...
- 多重纹理和纹理组合器
多重纹理和纹理组合器 本文主要介绍OpenGL中两种技术的使用方法:多重纹理技术和纹理组合器技术,最终根据参考[2]中的代码,实现了两个简单的演示DEMO,其中使用到了<八叉树颜色量化.BMP. ...
- 处理2D图像和纹理——投影纹理
创建一面镜子:投影纹理 问题 你想在场景中创建一面镜子.例如,在一个赛车游戏中创建一面后视镜.你也可以使用这个技术创建一个反射贴图. 解决方案 首先需要将镜子中看到的场景绘制到一张纹理中.然后,绘制相 ...
- shader入门精要读书笔记23 高级纹理-渲染纹理-镜子效果、玻璃效果
一.前言 现代GPU允许我们把整个三维场景目标渲染到一个中间缓冲中,即渲染目标纹理(RTT). 多重渲染目标(MRT),这种技术指的是GPU允许我们把场景同时渲染到多个渲染目标纹理中,而不再需要为每个 ...
- Unity Shader入门精要第七章 基础纹理渐变纹理
Unity系列文章目录 文章目录 Unity系列文章目录 前言 一.渐变纹理是什么 参考 前言 尽管在一开始,我们在渲染中使用纹理是为了定义一个物体的颜色,但后来人们发现,纹理 其实可以用于存储任何表 ...
最新文章
- CTFshow 信息收集 web9
- 凯里一中2021高考成绩查询,贵州凯里第一中学2021年排名
- 为什么你应该让你的孩子尽早学习编程
- Windows Developer Day - Windows AI Platform
- python实现局域网攻击_通过python实现DNS欺骗
- 马斯克疯狂套现,特斯拉市值一周蒸发1895亿美元!网友:丝毫不影响首富位置...
- nodeJS丶Buff使用及相关API
- java考勤表导出_考勤表如何导出?
- imx8mm yocto_bsp 编译
- C语言估算数学常量e,数学常数e
- 关于iPhone边缘触摸延迟现象
- [附源码]java毕业设计家政管理系统
- documents4j 实现Word文档、xlsx、等格式转换PDF文件
- 未来的量子计算机算圆周率吗,圆周率的诡异现象,圆周率算尽了会怎么样
- spring aop 切面添加日志
- Typora数学公式符号
- Win10+Python+virtualenv 环境配置
- 【游戏周边】Unity,UDK,Unreal Engine4或者CryENGINE——我应该选择哪一个游戏引擎
- 网站空间的流量是怎么算的?每月10G流量是什么意思,够用吗?
- Spark启动错误Exception in thread “main” java.lang.NoSuchMethodError: scala.collection.immutable.HashSet$