本文我们研究使用metal来加载图片,加载的图片我们区分tga格式的以及png和jpg格式的图片。加载tga跟png或者jpg格式的图片唯一区别在于图片解析部分,其余的完全相同。
流程步骤
1、SSShader类
设置MTKView
主要代码如下
-
ViewController
@interface ViewController () { MTKView *_view; SSRender *render; }
@end
@implementation ViewController
-
(void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view.
_view = (MTKView *)self.view; _view.device = MTLCreateSystemDefaultDevice(); render = [[SSRender alloc] initWithMTKView:_view]; _view.delegate = render; }
-
-
SSRender初始化
-(instancetype)initWithMTKView:(nonnull MTKView *)mtkView { self = [super init]; if (self) { _device = mtkView.device; _mView = mtkView; } return self; }
设置顶点数据
-(void)setVerture {
static const CCVertex quadVertices[] = {
//像素坐标,纹理坐标
{ { 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 } },
};
_vertexBuffer = [_device newBufferWithBytes:quadVertices length:sizeof(quadVertices) options:MTLResourceStorageModeShared];
_numVertices = sizeof(quadVertices) / sizeof(CCVertex);
}
设置渲染管道
渲染管道用来处理顶点、片元着色器,它们存储于.metal文件中
-(void)setupPipeLine
{
//1.创建我们的渲染通道
//从项目中加载.metal文件,创建一个library
id<MTLLibrary>defalutLibrary = [_device newDefaultLibrary];
//从库中加载顶点函数
id<MTLFunction>vertexFunction = [defalutLibrary newFunctionWithName:@"vertexShader"];
//从库中加载片元函数
id<MTLFunction> fragmentFunction = [defalutLibrary newFunctionWithName:@"fragmentShader"];
//2.配置用于创建管道状态的管道
MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
//管道名称
pipelineStateDescriptor.label = @"Texturing Pipeline";
//可编程函数,用于处理渲染过程中的各个顶点
pipelineStateDescriptor.vertexFunction = vertexFunction;
//可编程函数,用于处理渲染过程总的各个片段/片元
pipelineStateDescriptor.fragmentFunction = fragmentFunction;
//设置管道中存储颜色数据的组件格式
pipelineStateDescriptor.colorAttachments[0].pixelFormat = ccMTKView.colorPixelFormat;
//3.同步创建并返回渲染管线对象
NSError *error = NULL;
_pipelineState = [_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor error:&error];
//判断是否创建成功
if (!_pipelineState)
{
NSLog(@"Failed to created pipeline state, error %@", error);
}
//4.使用_device创建commandQueue
_commandQueue = [_device newCommandQueue];
}
加载纹理
-
tga纹理数据处理
-(void)setupTexture { //1.获取tag的路径 NSURL *imageFileLocation = [[NSBundle mainBundle] URLForResource:@"Image"withExtension:@"tga"]; //将tag文件->CCImage对象 CCImage *image = [[CCImage alloc]initWithTGAFileAtLocation:imageFileLocation]; //判断图片是否转换成功 if(!image) { NSLog(@"Failed to create the image from:%@",imageFileLocation.absoluteString); } //2.创建纹理描述对象 MTLTextureDescriptor *textureDescriptor = [[MTLTextureDescriptor alloc]init]; //表示每个像素有蓝色,绿色,红色和alpha通道.其中每个通道都是8位无符号归一化的值.(即0映射成0,255映射成1); textureDescriptor.pixelFormat = MTLPixelFormatBGRA8Unorm; //设置纹理的像素尺寸 textureDescriptor.width = image.width; textureDescriptor.height = image.height; //使用描述符从设备中创建纹理 _texture = [_device newTextureWithDescriptor:textureDescriptor]; //计算图像每行的字节数 NSUInteger bytesPerRow = 4 * image.width; /* typedef struct { MTLOrigin origin; //开始位置x,y,z MTLSize size; //尺寸width,height,depth } MTLRegion; */ //MLRegion结构用于标识纹理的特定区域。 demo使用图像数据填充整个纹理;因此,覆盖整个纹理的像素区域等于纹理的尺寸。 //3\. 创建MTLRegion 结构体 MTLRegion region = { {0,0,0}, {image.width,image.height,1} }; //4.复制图片数据到texture [_texture replaceRegion:region mipmapLevel:0 withBytes:image.data.bytes bytesPerRow:bytesPerRow]; }
代码中的CCImage类是用来处理tga文件,并返回纹理需要的width、height以及data数据
-
png或jpg纹理数据处理
-(void)setupPngTexture { UIImage *image = [UIImage imageNamed:@"yuanyuan.jpeg"]; if (!image) { NSLog(@"图片错误"); exit(0); } MTLTextureDescriptor *textureDescriptor = [[MTLTextureDescriptor alloc] init]; textureDescriptor.width = image.size.width; textureDescriptor.height = image.size.height; textureDescriptor.pixelFormat = MTLPixelFormatRGBA8Unorm; _texture = [_device newTextureWithDescriptor:textureDescriptor]; MTLRegion region = { {0, 0, 0}, {image.size.width, image.size.height, 1.0} }; Byte *imageByte = [self getByteImage:image]; if (imageByte) { [_texture replaceRegion:region mipmapLevel:0 withBytes:imageByte bytesPerRow:image.size.width * 4]; free(imageByte); imageByte = NULL; } } -(Byte *)getByteImage:(UIImage *)image { CGImageRef imgRef = image.CGImage; size_t width = CGImageGetWidth(imgRef); size_t height = CGImageGetHeight(imgRef); Byte *spriteData = (Byte *)calloc(width * height * 4, sizeof(Byte)); CGContextRef contentRef = CGBitmapContextCreate(spriteData, width, height, 8, 4 * width, CGImageGetColorSpace(imgRef), kCGImageAlphaPremultipliedLast); //contentRef上绘制图片 CGContextDrawImage(contentRef, CGRectMake(0, 0, width, height), imgRef); //图片翻转 CGRect rect = CGRectMake(0, 0, width, height); CGContextTranslateCTM(contentRef, rect.origin.x, rect.origin.y); CGContextTranslateCTM(contentRef, 0, rect.size.height); CGContextScaleCTM(contentRef, 1.0, -1.0); CGContextTranslateCTM(contentRef, -rect.origin.x, -rect.origin.y); CGContextDrawImage(contentRef, rect, imgRef); CGContextRelease(contentRef); return spriteData; }
Metal与OC公用的类
在这个类中主要定义一些metal与OC共同使用的一些结构体变量
// 缓存区索引值 共享与 shader 和 C 代码 为了确保Metal Shader缓存区索引能够匹配 Metal API Buffer 设置的集合调用
typedef enum CCVertexInputIndex
{
//顶点
CCVertexInputIndexVertices = 0,
//视图大小
CCVertexInputIndexViewportSize = 1,
} CCVertexInputIndex;
//纹理索引
typedef enum CCTextureIndex {
CCTextureIndexBaseColor = 0,
}CCTextureIndex;
//结构体: 顶点/纹理
typedef struct
{
// 像素空间的位置
// 像素中心点(100,100)
//float float
vector_float2 position;
// 纹理
vector_float2 textureCoordinate;
} CCVertex;
Metal类
metal中实现顶点着色器函数以及片元着色器函数的编写
#import "SSStruct.h"
// 顶点着色器输出和片段着色器输入
//结构体
typedef struct
{
//处理空间的顶点信息
float4 clipSpacePosition [[position]];
//纹理
float2 textureCoordinate;
} RasterizerData;
//顶点着色函数
vertex RasterizerData
vertexShader(uint vertexID [[vertex_id]],
constant CCVertex *vertices [[buffer(CCVertexInputIndexVertices)]],
constant vector_uint2 *viewportSizePointer [[buffer(CCVertexInputIndexViewportSize)]])
{
/*
处理顶点数据:
1) 执行坐标系转换,将生成的顶点剪辑空间写入到返回值中.
2) 将顶点颜色值传递给返回值
*/
//定义out
RasterizerData out;
//初始化输出剪辑空间位置
out.clipSpacePosition = vector_float4(0.0, 0.0, 0.0, 1.0);
// 索引到我们的数组位置以获得当前顶点
// 我们的位置是在像素维度中指定的.
float2 pixelSpacePosition = vertices[vertexID].position.xy;
//将vierportSizePointer 从verctor_uint2 转换为vector_float2 类型
vector_float2 viewportSize = vector_float2(*viewportSizePointer);
//每个顶点着色器的输出位置在剪辑空间中(也称为归一化设备坐标空间,NDC),剪辑空间中的(-1,-1)表示视口的左下角,而(1,1)表示视口的右上角.
//计算和写入 XY值到我们的剪辑空间的位置.为了从像素空间中的位置转换到剪辑空间的位置,我们将像素坐标除以视口的大小的一半.
out.clipSpacePosition.xy = pixelSpacePosition / (viewportSize / 2.0);
//把我们输入的颜色直接赋值给输出颜色. 这个值将于构成三角形的顶点的其他颜色值插值,从而为我们片段着色器中的每个片段生成颜色值.
out.textureCoordinate = vertices[vertexID].textureCoordinate;
//完成! 将结构体传递到管道中下一个阶段:
return out;
}
//当顶点函数执行3次,三角形的每个顶点执行一次后,则执行管道中的下一个阶段.栅格化/光栅化.
// 片元函数
//[[stage_in]],片元着色函数使用的单个片元输入数据是由顶点着色函数输出.然后经过光栅化生成的.单个片元输入函数数据可以使用"[[stage_in]]"属性修饰符.
//一个顶点着色函数可以读取单个顶点的输入数据,这些输入数据存储于参数传递的缓存中,使用顶点和实例ID在这些缓存中寻址.读取到单个顶点的数据.另外,单个顶点输入数据也可以通过使用"[[stage_in]]"属性修饰符的产生传递给顶点着色函数.
//被stage_in 修饰的结构体的成员不能是如下这些.Packed vectors 紧密填充类型向量,matrices 矩阵,structs 结构体,references or pointers to type 某类型的引用或指针. arrays,vectors,matrices 标量,向量,矩阵数组.
fragment float4 fragmentShader(RasterizerData in [[stage_in]],
texture2d<half> colorTexture [[texture(CCTextureIndexBaseColor)]])
{
constexpr sampler textureSampler(mag_filter::linear,
min_filter::linear);
const half4 colorSampler = colorTexture.sample(textureSampler, in.textureCoordinate);
//返回输入的片元颜色
return float4(colorSampler);
}