TGA纹理

① 效果展示

② 环境准备
  • 视图控制器类:在 viewDidLoad 函数中创建 MTKView 对象、自定义 render 对象,并设置 view 的代理为 render,其流程请参考:OpenGL之简单渲染一个三角形;
  • OC 与 C 的桥接文件:创建 metal 文件与 OC 通用的索引枚举(包括顶点数据索引、纹理索引) 和顶点数据的结构体,如下:
 typedef enum YDWVertexInputIndex {// 顶点YDWVertexInputIndexVertices = 0,// 视图大小YDWVertexInputIndexViewportSize = 1,}YDWVertexInputIndex;// 纹理索引:用于往片元着色器传递纹理数据的索引typedef enum YDWTextureIndex {YDWTextureIndexBaseColor = 0}YDWTextureIndex;// 顶点数据结构体:顶点/颜色值typedef struct{// 像素空间的位置// 像素中心点(100,100)// 顶点坐标vector_float2 position;// 2D 纹理// 纹理坐标vector_float2 textureCoordinate;}YDWVertex;
  • YDWImage 类:该类是将 TGA 转换为位图的工具类。由于平常的开发中很少去加载 TGA 文件,一般都是 PNG/JPG 文件,所以 TGA 文件转换成位图,其本质是将 TGA 文件的数据复制到 BGRA 图像数据指针指向的内存中。
  • 创建 metal 文件
    • 主要是1个结构体+两个函数,结构体中包含顶点坐标 和纹理坐标,作为定点着色器的输出以及片元着色器的输入;
 // 返回值结构体:顶点着色器输出和片元着色器输入(相当于OpenGL ES中的varying修饰的变量,即桥接)typedef struct {// 顶点坐标float4 clipSpacePosition [[position]];// 纹理坐标float2 textureCoordinate;}RasterizerData;
  • 顶点着色函数:主要是将顶点坐标归一化处理,并将处理后的顶点坐标和纹理坐标输出,经过 metal 的图元装配和光栅化处理,将顶点数据传入片元着色器,其流程图如下:
 vertex RasterizerDatavertexShader(uint vertexID [[vertex_id]],constant YDWVertex *vertexArray [[buffer(YDWVertexInputIndexVertices)]],constant vector_uint2 *viewportSizePointer [[buffer(YDWVertexInputIndexViewportSize)]]){/*处理顶点数据:1) 执行坐标系转换,将生成的顶点剪辑空间写入到返回值中.2) 将顶点颜色值传递给返回值*/// 1、定义outRasterizerData out;// 2、初始化输出剪辑空间位置,将w改为2.0,实际运行结果比1.0小一倍out.clipSpacePosition = vector_float4(0.0, 0.0, 0.0, 1.0);// 3、获取当前顶点坐标的xy,因为是2D图形// 索引到我们的数组位置以获得当前顶点// 我们的位置是在像素维度中指定的.float2 pixelSpacePosition = vertexArray[vertexID].position.xy;// 将vierportSizePointer 从verctor_uint2 转换为vector_float2 类型vector_float2 viewportSize = vector_float2(*viewportSizePointer);// 4、顶点坐标归一化处理// 每个顶点着色器的输出位置在剪辑空间中(也称为归一化设备坐标空间,NDC),剪辑空间中的(-1,-1)表示视口的左下角,而(1,1)表示视口的右上角.// 计算和写入 XY值到我们的剪辑空间的位置.为了从像素空间中的位置转换到剪辑空间的位置,我们将像素坐标除以视口的大小的一半.// 可以使用一行代码同时分割两个通道。执行除法,然后将结果放入输出位置的x和y通道中out.clipSpacePosition.xy = pixelSpacePosition / (viewportSize / 2.0);out.clipSpacePosition.z = 0.0f;out.clipSpacePosition.w = 1.0f;// 把我们输入的颜色直接赋值给输出颜色. 这个值将于构成三角形的顶点的其他颜色值插值,从而为我们片段着色器中的每个片段生成颜色值.// 纹理坐标桥接out.textureCoordinate = vertexArray[vertexID].textureCoordinate;// 完成! 将结构体传递到管道中下一个阶段return out;}
  • 片元着色函数:主要是通过采样器获取纹素,相当于 GLSL 中内建函数 texture2D,在 metal 中是通过纹理的 sample 函数获取,主要流程图如下:
 fragment float4 fragmentShader(RasterizerData in [[stage_in]],texture2d<half> colorTexture [[texture(YDWTextureIndexBaseColor)]]){// 当texture2d没有写access时,默认是sampler,// 设置采样器:过滤方式constexpr sampler textureSampler(mag_filter::linear, min_filter::linear);// 读取纹素(即纹理对应像素点的颜色值),相当于GLSL中的内建函数texture2D// half4取决于 texture2d<half>中的half ,由于颜色是RGBA,所以是half4// GLSL中属性的设置都是通过状态机,// 而metal中属性的设置是一个对象的思维,都是纹理采样的属性设置const half4 colorSampler = colorTexture.sample(textureSampler, in.textureCoordinate);// 进行灰度/...// 将half4 类型转换为 float4类型,如果不想这么麻烦,也可以将texture2d<half>中的half改为float,这样颜色的类型就是float4了return float4(colorSampler);}
  • 创建自定义render渲染循环类:苹果建议将渲染循环单独写成一个类,处理 metal 的渲染及委托事件,其中 TGA 文件的加载也是在 render 中处理的,它会将处理好的顶点数据及纹理图片传入着色器,着色器处理完成后会绘制并显示到屏幕上。
③ 渲染循环类
  • initWithMetalKitView 函数:

    • 初始化GPU设备:通过传入的view获取获取GPU的使用权限;
    • setupVertex函数:设置顶点相关操作;
    • setupPipeLine函数:设置渲染管道相关操作;
    • setupTexture函数:加载TGA文件;
    • setupVertex 函数:主要是出初始化顶点数据,包括顶点坐标和纹理坐标,当顶点坐标的范围不是-1~1时,是位于物体坐标系,需要在 metal 文件的顶点着色器中作归一化处理,并且将顶点数据存储到 MTLBuffer 对象中,GPU 函数流程如下:
 // 设置顶点相关操作- (void) setupVertex{// 1、根据顶点/纹理坐标建立一个MTLBufferstatic const YDWVertex quadVertices[] = {// 不是-1~1的都是物体坐标系// 像素坐标,纹理坐标{ {  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 } },};// 2、创建我们的顶点缓冲区,并用我们的Qualsits数组初始化它_vertexBuffer = [_device newBufferWithBytes:quadVertices length:sizeof(quadVertices) options:MTLResourceStorageModeShared];// 3、通过将字节长度除以每个顶点的大小来计算顶点的数目_numVertices = sizeof(quadVertices) / sizeof(YDWVertex); }
    • setupPipeLine 函数:主要是渲染管道相关的初始化工作,对应的流程图如下:
    • 渲染管道的初始化分为:

      • 加载metal文件;
      • 配置渲染管道;
      • 创建渲染管线对象;
      • 设置 commandQueue 命令对象。
 // 设置渲染管道相关操作- (void)setupPipeLine{// 1、创建渲染管道// 从项目中加载.metal文件,创建一个libraryid<MTLLibrary> defaultLibrary = [_device newDefaultLibrary];// 从库中加载顶点函数id<MTLFunction> vertexFunction = [defaultLibrary newFunctionWithName:@"vertexShader"];// 从库中加载片元函数id<MTLFunction> fragmentFunction = [defaultLibrary newFunctionWithName:@"fragmentShader"];// 2、配置用于创建渲染管道状态的管道MTLRenderPipelineDescriptor *renderPipelineDescriptor = [[MTLRenderPipelineDescriptor alloc] init];// 管道名称renderPipelineDescriptor.label = @"Texturing Pipeline";// 可编程函数,用于处理渲染过程中的各个顶点renderPipelineDescriptor.vertexFunction = vertexFunction;// 可编程函数,用于处理渲染过程总的各个片段/片元renderPipelineDescriptor.fragmentFunction = fragmentFunction;// 设置管道中存储颜色数据的组件格式renderPipelineDescriptor.colorAttachments[0].pixelFormat = ydwMTKView.colorPixelFormat;// 3、创建并返回渲染管线对象 & 判断是否创建成功NSError *error;_pipelineState = [_device newRenderPipelineStateWithDescriptor:renderPipelineDescriptor error:&error];if (!_pipelineState) {NSLog(@"Failed to created pipeline state, error %@", error);}// 4、使用device创建commandQueue_commandQueue = [_device newCommandQueue];}
    • setupTexture 函数:加载TGA图片,如下:
    • TGA 图片的加载分为以下步骤:
    • 获取 TGA 文件:主要是 TGA 文件通过转换为 YDWImage 对象,即纹理图片转换为位图:
     // 1、获取TGA文件路径 --- TGA文件解压NSURL *imageFileLocation = [[NSBundle mainBundle] URLForResource:@"circle" withExtension:@"tga"];// 将tag文件->YDWImage对象YDWImage *image = [[YDWImage alloc] initWithTGAFileAtLocation:imageFileLocation];// 判断图片是否转换成功if (!image) {NSLog(@"Failed to create the image from:%@",imageFileLocation.absoluteString);return;}
  • 创建纹理描述对象 & 纹理对象:纹理对象通过创建的纹理描述对象以及解压的位图生成,其中需要设置纹理描述对象的相关属性:
     // 2、创建纹理描述对象 & 设置属性 --- YDWImage --> 纹理(即位图变成纹理对象)MTLTextureDescriptor *textureDescriptor = [[MTLTextureDescriptor alloc] init];// 表示每个像素有蓝色,绿色,红色和alpha通道.其中每个通道都是8位无符号归一化的值.(即0映射成0,255映射成1);// 位图信息textureDescriptor.pixelFormat = MTLPixelFormatBGRA8Unorm;// 设置纹理的像素尺寸,即纹理的分辨率textureDescriptor.width = image.width;textureDescriptor.height = image.height;// 3、创建纹理对象:使用描述符从设备中创建纹理_texture = [_device newTextureWithDescriptor:textureDescriptor];
  • 将纹理图片复制到 texture 对象中:在复制纹理图片之前,首先需要计算图像每行的字节数,以及设置纹理对应的像素区域region,region的定义如下,其中包含两个结构体对象origin和size,其中origin表示开始的顶点坐标(x,y,z),size表示尺寸(宽度、高度、深度):
 typedef struct {MTLOrigin origin; // 开始位置x,y,zMTLSize   size;   // 尺寸width,height,depth} MTLRegion;
  • 然后通过replaceRegion: mipmapLevel: withBytes: bytesPerRow:函数将图片复制到纹理对象中:
     // 计算图像每行的字节数NSUInteger bytesPerRow = 4 * image.width;// 4、创建MTLRegion结构体/*typedef struct{MTLOrigin origin; //开始位置x,y,zMTLSize   size; //尺寸width,height,depth} MTLRegion;*/// MLRegion结构用于标识纹理的特定区域。 demo使用图像数据填充整个纹理;因此,覆盖整个纹理的像素区域等于纹理的尺寸。MTLRegion region = {{0,0,0},{image.width, image.height, 1},};// 5、复制图片数据到texture/*将图片复制到纹理0中(即用纹理替换region表示的区域)- (void)replaceRegion:(MTLRegion)region mipmapLevel:(NSUInteger)level withBytes:(const void *)pixelBytes bytesPerRow:(NSUInteger)bytesPerRow;参数1-region:像素区域在纹理中的位置参数2-level:从零开始的值,指定哪个mipmap级别是目标。如果纹理没有mipmap,请使用0。参数3-pixelBytes:指向要复制图片的字节数参数4-bytesPerRow:对于普通或压缩像素格式,源数据行之间的跨度(以字节为单位)。对于压缩像素格式,跨度是从一排块的开头到下一行的开始的字节数。*/[_texture replaceRegion:region mipmapLevel:0 withBytes:image.data.bytes bytesPerRow:bytesPerRow];****
④ MTKViewDelegate 代理方法
  • delegate的代理方法有两种,这里主要是说明绘制drawInMTKView代理方法,其流程图如下:

  • 向着色器中传递的数据有三种:

    • 顶点数据:包含顶点坐标和纹理坐标,由于顶点数据是存储在缓存区中的,所以需要通过setVertexBuffer函数传递到顶点着色函数中;
    • 视图大小数据:需要通过setVertexBytes函数传递到顶点着色器中;
    • 纹理图片:将纹理对象加载到GPU中,需要通过setFragmentTexture函数传递到片元着色函数,通过采样器读物纹素,加载TGA图片。
  • 传递数据对应的代码如下:
 /*需要传递的数据有以下三种:1)顶点数据、纹理坐标,2)viewportSize视图大小3)纹理图片*/// 将数据加载到MTLBuffer (即metal文件中的顶点着色函数)--> 顶点函数[commandEncoder setVertexBuffer:_vertexBuffer offset:0 atIndex:YDWVertexInputIndexVertices];//将数据加载到GPU(即metal文件中的顶点着色函数) --> 视图大小[commandEncoder setVertexBytes:&_viewportSize length:sizeof(_viewportSize) atIndex:YDWVertexInputIndexViewportSize];//将纹理对象传递到片元着色器(即metal中的片元着色函数) -- 纹理图片[commandEncoder setFragmentTexture:_texture atIndex:YDWTextureIndexBaseColor];
⑤ 整体流程

PNG/JPEG纹理

  • 与加载 TGA 图片相比,在原代码基础上,需要将 setupTexture 函数修改为 setupTexturePNG 函数;
① setupTexturePNG函数
  • 该函数的功能是加载PNG/JPG图片,其加载的流程如图所示:

② loadImage 函数
  • 除了获取图片以及loadImage函数,其他步骤与加载TGA图片时,步骤是一致的,将PNG/JPG图片加载成位图的,通过CGContextRef对象将图片进行重绘,解压成位图数据的,图片解压为位图的流程如图所示:

③ 实现如下
// 加载纹理TGA文件
- (void)setupTexturePNG{// 1、获取图片UIImage *image = [UIImage imageNamed:@"mouse.jpg"];// 2、创建纹理描述符MTLTextureDescriptor *textureDescriptor = [[MTLTextureDescriptor alloc] init];// 表示每个像素有蓝色,绿色,红色和alpha通道.其中每个通道都是8位无符号归一化的值.(即0映射成0,255映射成1);textureDescriptor.pixelFormat = MTLPixelFormatRGBA8Unorm;// 设置纹理的像素尺寸textureDescriptor.width = image.size.width;textureDescriptor.height = image.size.height;// 3、使用纹理描述符创建纹理_texture = [_device newTextureWithDescriptor:textureDescriptor];// 4、创建MTLRegion对象// MLRegion结构用于标识纹理的特定区域。 demo使用图像数据填充整个纹理;因此,覆盖整个纹理的像素区域等于纹理的尺寸。/*typedef struct{MTLOrigin origin; //开始位置x,y,zMTLSize   size; //尺寸width,height,depth (即宽、高、深度)} MTLRegion;*/MTLRegion region = {{0, 0, 0},{image.size.width, image.size.height, 1},};// 5、获取纹理图片:通过context重绘获取纹理图片Byte *imageBytes = [self loadImage:image];// 6、UIImage的数据需要转成二进制才能上传,且不用jpg、png的NSDataif (imageBytes) {// 将纹理图片复制到texture[_texture replaceRegion:region mipmapLevel:0 withBytes:imageBytes bytesPerRow:4 * image.size.width];// 释放free(imageBytes);imageBytes = NULL;}
}// 从UIImage 中读取Byte 数据返回 -- png/jpg 都是通过context重绘 解压成位图- (Byte *)loadImage:(UIImage *)image{// 1、将UIImage转换为CGImageRefCGImageRef spriteImage = image.CGImage;// 2、读取图片的大小size_t width = CGImageGetWidth(spriteImage);size_t height = CGImageGetHeight(spriteImage);// 3、计算图片字节数Byte *spriteData = (Byte *)calloc(width * height * 4, sizeof(Byte));// 4、创建contextCGContextRef spriteContext = CGBitmapContextCreate(spriteData, width, height, 8, width*4, CGImageGetColorSpace(spriteImage), kCGImageAlphaPremultipliedLast);// 5、在context上绘图CGContextDrawImage(spriteContext, CGRectMake(0, 0, width, height), spriteImage);// 6、图片翻转过来CGRect rect = CGRectMake(0, 0, width, height);CGContextTranslateCTM(spriteContext, 0, rect.size.height);CGContextScaleCTM(spriteContext, 1.0, -1.0);CGContextDrawImage(spriteContext, rect, spriteImage);// 7、释放contextCGContextRelease(spriteContext);return spriteData;
}
④ 总结

根据TGA图片加载以及PNG/JPG图片加载过程的分析,如下:

  • 将纹理图片解压成位图;
  • 创建 MTLTextureDescriptor 对象,即纹理描述对象,并设置 pixelFormat 像素信息、width以及height尺寸信息;
  • 使用纹理描述对象创建MTLTexture对象,即纹理对象;
  • 创建MTLRegion对象,用于标识纹理的像素区域;
  • 通过texture的replaceRegion:mipmapLevel:withBytes:bytesPerRow:函数将纹理图片解压的位图数据复制到纹理对象中;
  • 绘制回调函数drawInMTKView中,通过MTLrenderCommandEncoder对象的setFragmentTexture:atIndex:函数,将纹理传递到片元着色函数。

Metal之加载TGA与PNG/JPEG纹理图片相关推荐

  1. Three.js - 加载 TGA 格式的纹理

    1.TGA格式 TGA(Targa)格式是计算机上应用最广泛的图象格式. 在兼顾了BMP的图象质量的同时又兼顾了JPEG的体积优势. 并且还有自身的特点:通道效果.方向性. 在CG领域常作为影视动画的 ...

  2. 【我的OpenGL学习进阶之旅】C++如何加载TGA文件?

    一.TGA文件相关介绍 通过前面的博客 [我的OpenGL学习进阶之旅]什么是TGA文件以及如何打开TGA文件? 地址:https://ouyangpeng.blog.csdn.net/article ...

  3. Android 高清加载巨图方案 拒绝压缩图片

    Android 高清加载巨图方案 拒绝压缩图片 转载请标明出处:  http://blog.csdn.net/lmj623565791/article/details/49300989:  本文出自: ...

  4. iOS原生如何加载HTML中img标签的图片

    原文出自:iOS原生如何加载HTML中img标签的图片 前言 最近iOS App项目中使用Webview加载H5页面比较多,也有不少朋友经常问到这个问题,在这里我也学习学习如何通过iOS原生的方式来加 ...

  5. C#实现网页加载后将页面截取成长图片 | Playwright版

    前言 如何将网页生成预览图? 要实现这个功能,可以用WebBrowser组件模拟浏览器,或者使用系统浏览器访问网页,再进行截图操作. 但是,这样需要编写大量的控制代码. 工欲善其事,必先利其器! 利用 ...

  6. [html] img中的src加载失败时如何用默认图片来替换呢?

    [html] img中的src加载失败时如何用默认图片来替换呢? img有onerror属性,加载失败时触发error事件 但是这种解决方法在error里面替换的默认图片也加载失败的时候会导致问题,需 ...

  7. 当原图片加载失败时,如何让图片加载上我们默认给的图片

    我们可能会遇到这样的问题,当页面中的图片的加载失败时,我们想要该图片加载我们给的默认的图片,我在这里分享一下几种做法,希望对大家有所帮助. 1.在img标签中加上 οnerrοr="this ...

  8. ArcGIS如何加载水经注下载的图片?

    用户"*向荣"的疑问:ArcGIS如何加载水经注下载的图片? 水经注的解答:在下载任务时选择,在"导出设置"对话框中选择保存选项为"GeoTIF(*. ...

  9. 高清加载巨图方案-拒绝压缩图片

    Android 高清加载巨图方案 拒绝压缩图片 转载请标明出处: http://blog.csdn.net/lmj623565791/article/details/49300989: 本文出自:[张 ...

最新文章

  1. nginx basic auth配置踩坑记
  2. 集合到文件数据排序改进版
  3. QDD pricing determination does not trigger - set breakpoint to resolve it
  4. (转载)Qt中MOC的一些限制
  5. Linux系统查看内存的几个小命令
  6. intouch负值显示0_17、定位的盒子居中显示
  7. 05-2_部署 kube-apiserver 集群
  8. SQL Server数据库优化的几种方法.
  9. OPPO小布助手算法系统的探索、实践与思考
  10. 职业学校计算机课评课,中职计算机评课稿
  11. 图像的旋转——imrotate
  12. 企业PC终端安全问题分析及整改措施
  13. MATLAB-蒙特卡罗方法
  14. 网页去广告服务器,使用 AdGuardHome,实现网页加速和去广告
  15. 文本学习-《背影》-朱自清
  16. opencv 读取双摄自动对齐参数intrinsics.yml、extrinsics.yml 2021-04-12
  17. Python实现中文转拼音功能
  18. 【文献管理软件Zotero】Zotfile插件及云同步的使用技巧
  19. WML语言基础(WAP建站)一(转)
  20. python的基本语法、数据类型、运算符及基本操作_Python教程基础语法、变量基本使用和算术运算符...

热门文章

  1. Message 消息提示
  2. Linux使用imagemagick的convert命令压缩图片、节省服务器空间
  3. 动态规划之一最长上升子序列LIS
  4. SharePoint 2007 开发系列(14) 调试sharepoint web part
  5. 如何在dw上编写php_用dw制作php网站视频教程
  6. MySQL根被拒绝_[转载]phpMyAdmin 尝试连接到 MySQL 服务器,但服务器拒绝连接。...
  7. 每日程序C语言5-斐波那契兔子数
  8. oracle rac 节点启动,Oracle 10g RAC 节点自动重启故障处理
  9. java一维数组的特点,数据结构:java数组特点以及声明数组类
  10. labview连接mysql数据库_labview使用DSN与数据库的连接包括access,mysql