Metal 創(chuàng)建和采樣紋理

您可以使用紋理在Metal中繪制和處理圖像。紋理是紋理元素的結構化集合,通常稱為紋理元素或像素。這些紋理元素的確切配置取決于紋理的類型。此示例使用一個由2D元素數組構成的紋理來保存圖像,每個元素都包含顏色數據。紋理通過稱為紋理映射的過程繪制到幾何圖元上。片段函數通過采樣紋理為每個片段生成顏色。

紋理由MTLTexture對象管理。一個MTLTexture對象定義了紋理的格式,包括元素的大小和布局,紋理中元素的數量,以及這些元素的組織方式。一旦創(chuàng)建,紋理的格式和組織永遠不會改變。但是,可以通過渲染紋理或將數據復制到紋理中來更改紋理的內容。

Metal框架沒有提供一個應用編程接口來直接將圖像數據從文件加載到紋理中。Metal本身只分配紋理資源,并提供從紋理復制數據的方法。Metal應用依賴于定制代碼或其他框架,如Metal工具包、圖像輸入/輸出、UIKit或應用工具包來處理圖像文件。例如,您可以使用MTKTextureLoader來執(zhí)行簡單的紋理加載。此示例顯示了如何編寫自定義紋理加載器。

加載和格式化圖像數據

在示例中,AAPLImage類從TGA文件中加載和解析圖像數據。該類將TGA文件中的像素數據轉換為Metal能夠理解的像素格式。該示例使用圖像的元數據創(chuàng)建新的Metal紋理,并將像素數據復制到紋理中。

Metal要求所有紋理都用特定的像素格式值進行格式化。像素格式描述了紋理中像素數據的布局。本示例使用的是MTLPixelFormatBGRA8Unorm無序像素格式,即每個像素使用32位,按藍、綠、紅和阿爾法順序排列為每個組件8位:


image

在填充Metal紋理之前,必須將圖像數據格式化為紋理的像素格式。TGA文件可以提供32位/像素格式或24位/像素格式的像素數據。每個像素使用32位的TGA文件已經以這種格式排列,所以您只需復制像素數據。若要轉換每像素24位的BGR圖像,請復制紅色、綠色和藍色通道,并將alpha通道設置為255,表示完全不透明的像素。

// 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++)
{
    // 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;

從紋理描述符創(chuàng)建紋理

使用MTLTextureDescriptor對象為MTLTexture對象配置紋理尺寸和像素格式等屬性。然后調用newTextureWithDescriptor:方法創(chuàng)建一個紋理。

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<MTLTexture> texture = [_device newTextureWithDescriptor:textureDescriptor];

Metal創(chuàng)建一個MTLTexture對象并為紋理數據分配內存。創(chuàng)建紋理時,該內存未初始化,因此下一步是將數據復制到紋理中。

將圖像數據復制到紋理中

Metal管理紋理的內存,并且不提供直接訪問它的權限。因此,您將無法獲得指向內存中紋理數據的指針并自行復制像素。相反,您可以在MTLTexture對象上調用方法以從內存中復制數據,然后可以將其訪問到紋理中,反之亦然。

在此示例中,AAPLImage對象為圖像數據分配了內存,因此您將告訴紋理對象復制該數據。

使用MTLRegion結構來標識要更新紋理的哪一部分。該示例使用圖像數據填充整個紋理。因此,創(chuàng)建一個覆蓋整個紋理的區(qū)域。

MTLRegion region = {
    { 0, 0, 0 },                   // MTLOrigin
    {image.width, image.height, 1} // MTLSize
};

圖像數據通常按行組織,并且您需要告訴Metal在源圖像中行之間的偏移量。圖像加載代碼以緊密打包的格式創(chuàng)建圖像數據,因此后續(xù)像素行的數據緊隨前一行。將行之間的偏移量計算為一行的確切長度(以字節(jié)為單位),即每像素的字節(jié)數乘以圖像寬度。

NSUInteger bytesPerRow = 4 * image.width;

調用紋理上的replaceRegion:mimapLevel:withBytes:Bytesperrow:方法將像素數據從AAPLImage對象復制到紋理中。

[texture replaceRegion:region
            mipmapLevel:0
              withBytes:image.data.bytes
            bytesPerRow:bytesPerRow];

將紋理映射到幾何圖元上

你不能單獨渲染一個紋理;您必須將其映射到由頂點階段輸出并由光柵化器轉換成片段的幾何圖元(在本例中為一對三角形)上。每個片段都需要知道紋理的哪一部分應該應用于它。您可以使用紋理坐標定義這種映射:將紋理圖像上的位置映射到幾何表面上的位置的浮點位置。

對于2D紋理,歸一化紋理坐標在x和y方向上都是從0.0到1.0的值。值(0.0,0.0)指定紋理數據第一個字節(jié)的紋理元素(圖像的左上角)。值(1.0,1.0)指定紋理數據最后一個字節(jié)的紋理元素(圖像的右下角)。


image

向頂點格式添加一個字段來保存紋理坐標:

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 } },
};

要將紋理坐標發(fā)送到片段著色器,請向RasterizerData數據結構添加紋理坐標值:

typedef struct
{
    // 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;

} RasterizerData;

在頂點著色器中,通過將紋理坐標寫入textureCoordinate字段,將紋理坐標傳遞給光柵化器階段。光柵化器階段在四邊形的三角形片段上插入這些坐標。

out.textureCoordinate = vertexArray[vertexID].textureCoordinate;

從紋理中的位置計算顏色

您可以對紋理進行采樣,從紋理中的某個位置計算顏色。為了采樣紋理數據,片段函數需要紋理坐標和對要采樣的紋理的引用。除了從光柵化器階段傳入的參數之外,還應傳入一個帶有texture2d類型和[[texture(index)]屬性限定符的colorTexture參數。此參數是對要采樣的MTLTexture對象的引用。

fragment float4
samplingShader(RasterizerData in [[stage_in]],
               texture2d<half> colorTexture [[ texture(AAPLTextureIndexBaseColor) ]])

使用內置的紋理采樣函數來采集紋理像素數據,該sample()函數有兩個參數:一個用于描述如何采樣的采樣器和一個用于描述要采樣的紋理位置的紋理坐標。該函數從紋理中提取一個或多個像素,并返回根據這些像素計算出的顏色。

當要渲染的區(qū)域與紋理的大小不同時,采樣器可以使用不同的算法阿萊精確計算sample()函數應該返回的紋理顏色,設置mag_filter模式以指定當面積大于紋理尺寸時采樣器應如何計算返回顏色,設置min_filter模式以指定當面積小于紋理尺寸時采樣器應如何計算返回顏色。為兩個濾鏡設置線性模式會使采樣器平均給定紋理坐標周圍像素的顏色,從而產生更平滑的輸出圖像。

constexpr sampler textureSampler (mag_filter::linear,
                                  min_filter::linear);

// Sample the texture to obtain a color
const half4 colorSample = colorTexture.sample(textureSampler, in.textureCoordinate);

編碼繪圖參數

編碼和提交繪圖命令的過程與使用渲染管道渲染圖元中顯示的過程相同,因此下面沒有顯示完整的代碼。此示例的不同之處在于片段著色器有一個附加參數。當您編碼命令的參數時,設置片段函數的紋理參數。此示例使用AAPLtextureIndex基底顏色索引來識別目標C和Metal著色語言代碼中的紋理。

[renderEncoder setFragmentTexture:_texture
                          atIndex:AAPLTextureIndexBaseColor];
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容