本案例的讲解是在 Metal 绘制网格 案例基础上的讲解。
首先说明下改案例需要的几个类:
-
CQMetalTextureVC
:纹理控制器。用于初始化MTKView
和CQTextureRender
。 -
CQTexture.metal
:编写MSL
程序。 -
CQTextureRender
:负责纹理渲染的类。 -
CQTexture.h
:用于定义.metal
和CQTextureRender
共用的数据类型。
一、CQTexture.metal
代码:
#include <metal_stdlib>
using namespace metal;
#import "CQTexture.h"
struct VertexOut {
float4 clipSpacePosition [[position]];
float2 textureCoordinate;
};
vertex VertexOut vertexShaderTexture(uint vertexIndex [[vertex_id]],
constant CQVertexTextureInput *vertexInput [[buffer(CQVInTextureIndexVertices)]],
constant vector_uint2 *viewportSizePointer [[buffer(CQVInIndexViewportSize)]]) {
//取出顶点坐标的xy,该案例中的位置是在像素维度中指定的。
float2 pixelSpacePosition = vertexInput[vertexIndex].position.xy;
vector_float2 viewportSize = vector_float2 (*viewportSizePointer);
float2 xy = pixelSpacePosition / (viewportSize / 2.0);
VertexOut out;
out.clipSpacePosition.xy = xy;
out.clipSpacePosition.zw = vector_float2(0.0, 1.0);
out.textureCoordinate = vertexInput[vertexIndex].textureCoordinate;
return out;
}
fragment float4 fragmentShaderTexture(VertexOut in [[stage_in]],
texture2d<half> colorTexture [[texture(CQTextureIndexBaseColor)]]) {
constexpr sampler textureSampler(min_filter::linear,
mag_filter::linear);
const half4 colorSampler = colorTexture.sample(textureSampler, in.textureCoordinate);
return float4(colorSampler);
}
该案例中顶点函数vertexShaderTexture
中的代码跟Metal 绘制网格 中的顶点函数代码一致。
片元函数fragmentShaderTexture
中我们传了一个参数:colorTexture
-
texture2d<half> colorTexture [[texture(CQTextureIndexBaseColor)]]
该参数是用来传递我们的纹理。 -
constexpr
:在Metal
程序中初始化的采样器sampler
必须使用constexpr
修饰符声明。 -
min_filter::linear
:线性缩小过滤模式。 -
mag_filter::linear
:线性放大过滤模式。
二、CQTextureRender.m
代码:
看下纹理渲染的准备工作:
- (instancetype)initWithMetalKitView:(MTKView *)mtkView {
self = [super init];
if(self) {
_mtkView = mtkView;
[self setupVertices];
[self setupPipeline];
[self loadTGATexture];
}
return self;
}
- 1.设置顶点。
- 2.设置管线。
- 3.加载纹理。
2.1设置顶点:
- (void)setupVertices {
static const CQVertexTextureInput vertices[] = {
//像素坐标,纹理坐标
{ { 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 } },
};
_verticesBuffer = [_mtkView.device newBufferWithBytes:vertices length:sizeof(vertices) options:MTLResourceStorageModeShared];
_verticesNumber = sizeof(vertices)/sizeof(CQVertexTextureInput);
}
-
_verticesBuffer
:创建的顶点缓冲区MTLBuffer
。 -
_verticesNumber
:顶点数量。
2.设置管线:
- (void)setupPipeline {
//加载所有的metal文件
id<MTLLibrary> defaultLibrary = [_mtkView.device newDefaultLibrary];
id<MTLFunction> vertexShaderTexture = [defaultLibrary newFunctionWithName:@"vertexShaderTexture"];
id<MTLFunction> fragmentShaderTexture = [defaultLibrary newFunctionWithName:@"fragmentShaderTexture"];
MTLRenderPipelineDescriptor *renderPipelineDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
renderPipelineDescriptor.vertexFunction = vertexShaderTexture;
renderPipelineDescriptor.fragmentFunction = fragmentShaderTexture;
renderPipelineDescriptor.colorAttachments[0].pixelFormat = _mtkView.colorPixelFormat;
NSError *error;
_renderPipelineState = [_mtkView.device newRenderPipelineStateWithDescriptor:renderPipelineDescriptor error:&error];
if (!_renderPipelineState) {
NSLog(@"Make render pipeline state error:%@", error);
return;
}
_commandQueue = [_mtkView.device newCommandQueue];
}
3.1加载tga
格式纹理:
- (void)loadTGATexture {
//1.获取图片
NSURL *imageUrl = [[NSBundle mainBundle] URLForResource:@"Image" withExtension:@"tga"];
CCImage *image = [[CCImage alloc] initWithTGAFileAtLocation:imageUrl];
// AAPLImage *image = [[AAPLImage alloc] initWithTGAFileAtLocation:imageUrl];
if(!image) {
NSLog(@"Failed to create the image from:%@",imageUrl.absoluteString);
}
//2.创建纹理描述符
MTLTextureDescriptor *textureDescriptor = [[MTLTextureDescriptor alloc] init];
textureDescriptor.width = image.width;
textureDescriptor.height = image.height;
textureDescriptor.pixelFormat = MTLPixelFormatRGBA8Unorm;
//3.创建纹理
//识别图像中的像素。MTLOrigin通常用作纹理区域的左上角。
MTLOrigin origion = MTLOriginMake(0, 0, 0);
MTLSize size = MTLSizeMake(image.width, image.height, 1);
MTLRegion region = {origion, size};
_texture = [_mtkView.device newTextureWithDescriptor:textureDescriptor];
[_texture replaceRegion:region mipmapLevel:0 withBytes:image.data.bytes bytesPerRow:image.width * 4];
}
-
CCImage
:自定义的类处理tga
格式的图片。
大家可以参考一下官方案例 Creating and Sampling Textures 中AAPLImage
类对tga
格式的图片处理。 -
MTLRegion
是个结构体:用于标识纹理的特定区域,本案例使用图像数据填充整个纹理。因此,覆盖整个纹理的像素区域等于纹理的尺寸。
typedef struct {
MTLOrigin origin; //开始位置x,y,z
MTLSize size; //尺寸width,height,depth
} MTLRegion;
-
_texture
:通过纹理描述符创建的纹理对象id<MTLTexture>
。
- (void)replaceRegion:(MTLRegion)region mipmapLevel:(NSUInteger)level withBytes:(const void *)pixelBytes bytesPerRow:(NSUInteger)bytesPerRow;
region
:像素区域在纹理中的位置
level
:从零开始的值,指定哪个mipmap
级别的目标。如果纹理没有mipmap
,使用0。
pixelBytes
:指向要复制图片的字节数。
bytesPerRow
:每一行像素的所占字节大小。
3.2加载png、jpg
格式纹理:
-(void)loadPNGTexture {
UIImage *image = [UIImage imageNamed:@"scence.png"];
MTLTextureDescriptor *textureDescriptor = [[MTLTextureDescriptor alloc] init];
//表示每个像素有蓝色,绿色,红色和alpha通道.其中每个通道都是8位无符号归一化的值.(即0映射成0,255映射成1);
textureDescriptor.pixelFormat = MTLPixelFormatRGBA8Unorm;
textureDescriptor.width = image.size.width;
textureDescriptor.height = image.size.height;
_texture = [_mtkView.device newTextureWithDescriptor:textureDescriptor];
MTLOrigin origion = MTLOriginMake(0, 0, 0);
MTLSize size = MTLSizeMake(image.size.width, image.size.height, 1);
MTLRegion region = {origion, size};
//获取图片数据
Byte *imageBytes = [self loadImage:image];
//UIImage的数据需要转成二进制才能上传,且不用jpg、png的NSData
if (imageBytes) {
[_texture replaceRegion:region
mipmapLevel:0
withBytes:imageBytes
bytesPerRow:4 * image.size.width];
free(imageBytes);
imageBytes = NULL;
}
}
-
UIImage
的数据需要转成二进制Byte
才能上传,且不用jpg、png
的NSData
。 - 获取图片数据
Byte
:
- (Byte *)loadImage:(UIImage *)image {
// 1.获取图片的CGImageRef
CGImageRef spriteImage = image.CGImage;
// 2.读取图片的大小
size_t width = CGImageGetWidth(spriteImage);
size_t height = CGImageGetHeight(spriteImage);
//3.计算图片大小.rgba共4个byte
Byte * spriteData = (Byte *)calloc(width * height * 4, sizeof(Byte));
//4.创建画布
CGContextRef spriteContext = CGBitmapContextCreate(spriteData, width, height, 8, width*4, CGImageGetColorSpace(spriteImage), kCGImageAlphaPremultipliedLast);
//5.在CGContextRef上绘图
CGContextDrawImage(spriteContext, CGRectMake(0, 0, width, height), spriteImage);
//6.图片翻转过来
CGRect rect = CGRectMake(0, 0, width, height);
CGContextTranslateCTM(spriteContext, rect.origin.x, rect.origin.y);
CGContextTranslateCTM(spriteContext, 0, rect.size.height);
CGContextScaleCTM(spriteContext, 1.0, -1.0);
CGContextTranslateCTM(spriteContext, -rect.origin.x, -rect.origin.y);
CGContextDrawImage(spriteContext, rect, spriteImage);
//7.释放spriteContext
CGContextRelease(spriteContext);
return spriteData;
}
4.MTKViewDelegate
绘制纹理
- (void)drawInMTKView:(nonnull MTKView *)view {
id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
commandBuffer.label = @"TextureCommandBuffer";
MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor;
if (renderPassDescriptor) {
id<MTLRenderCommandEncoder> renderCommandEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
renderCommandEncoder.label = @"TextureRenderCommandEncoder";
MTLViewport viewport = {0.0, 0.0, _viewportSize.x, _viewportSize.y, -1.0, 1.0};
[renderCommandEncoder setViewport:viewport];
[renderCommandEncoder setRenderPipelineState:_renderPipelineState];
[renderCommandEncoder setVertexBuffer:_verticesBuffer offset:0 atIndex:CQVInTextureIndexVertices];
[renderCommandEncoder setVertexBytes:&_viewportSize length:sizeof(_viewportSize) atIndex:CQVInIndexViewportSize];
[renderCommandEncoder setFragmentTexture:_texture atIndex:CQTextureIndexBaseColor];
[renderCommandEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:_verticesNumber];
[renderCommandEncoder endEncoding];
[commandBuffer presentDrawable:view.currentDrawable];
}
[commandBuffer commit];
}
- (void)mtkView:(nonnull MTKView *)view drawableSizeWillChange:(CGSize)size {
_viewportSize.x = size.width;
_viewportSize.y = size.height;
}
-
- (void)setVertexBuffer:(nullable id <MTLBuffer>)buffer offset:(NSUInteger)offset atIndex:(NSUInteger)index;
:
将顶点缓冲区_verticesBuffer
的顶点数据传递到顶点函数指定顶点的索引CQVInTextureIndexVertices
。 -
- (void)setVertexBytes:(const void *)bytes length:(NSUInteger)length atIndex:(NSUInteger)index;
:
为给定 绑定位置的 顶点缓冲区 设置顶点数据(通过复制)。这将从 绑定的位置 移除任何现有的MTLBuffer
。
该案例的目的是利用该函数 将视口大小的指针地址 传递到顶点函数。 -
- (void)setFragmentTexture:(nullable id <MTLTexture>)texture atIndex:(NSUInteger)index;
:
在给定绑定位置索引的地方为所有的片元着色器设置全局的纹理。
绘制效果:
网友评论