Metal(三)- Swift案例:三角形绘制

相比于上一篇helloWorld,这一篇内容增加了顶点数据和Metal的内容。

效果图

绘制流程:


绘制流程

具体代码实现

1,Metal文件

#import "HrShaderType.h"

typedef struct {
    //处理空间的顶点信息
    //position是关键字,类似于GLSL中的gl_Position
    float4 clipSpacePosition [[position]];

    //颜色
    float4 color;
} RasterizerData;
vertex函数
vertex RasterizerData vertexShader(uint vertexId [[vertex_id]],
                                   constant HRVertex *vertexs [[buffer(VertexInputIndexVertices)]],
                                   constant vector_float2 *viewportSize [[buffer(VertexInputIndexViewPortSize)]]) 
{
    RasterizerData out;
    
    out.clipSpacePosition = vertexs[vertexId].position;
    //把我们输入的颜色直接赋值给输出颜色.通过这种方式将颜色数据桥接到片元着色器
    out.color = vertexs[vertexId].color;
    
    return out;
}
  • vertex:函数限定符,限定该函数为顶点函数
  • RasterizerData:函数返回值,会将该参数经过光栅化后传递到片元函数
  • vertexShader:函数自定义名称
  • uint vertexId [[vertex_id]]:
    • uint变量类型:无符号32位整型;
    • vertexId变量名;
    • [[vertex_id]]属性修饰符:代表顶点编号固定写法,开发者不得修改
  • constant HRVertex *vertexs [[buffer(VertexInputIndexVertices)]]:
    • constant变量限定符:存储在GPU的常量缓存区中;
    • HRVertex:变量类型; vertexs:变量名;
fragment函数
fragment float4 fragmentShader(RasterizerData in [[stage_in]]) {
    //返回该像素点的色值
    return in.color;
}
  • fragment函数限定符:片元函数
  • float4:返回值,代表颜色RGBA
  • fragmentShader:函数名
  • RasterizerData in [[stage_in]]:
    • RasterizerData变量类型;
    • in变量名;
    • [[stage_in]]属性修饰符:片元着色函数使用的单个片元输入数据是由顶点着色函数输出.然后经过光栅化生成的.

2,桥接文件

由于需要在Swift文件中使用OC头文件,需要通过桥接文件XXX-Bridging-Header来导入.h文件

//定义了基本的向量、矩阵、四元数,该头文件同时存在于Metal Shader / swift | Objc中,方便相互传递数据
#include <simd/simd.h>

//该文件作用:通过文件引入的方式,将一些自定义的类型声明既传递到swift文件,同时也传递到metal文件中
typedef struct {
    vector_float4 position;
    vector_float4 color;
} HRVertex;

typedef enum {
    //顶点数据
    VertexInputIndexVertices = 0,
    //视图大小
    VertexInputIndexViewPortSize = 1,
}VertexInputIndex;

3,自定义Render渲染类

后续用到的相关全局参数
    private var _device : MTLDevice?
    private var commandQueue : MTLCommandQueue?
    private var pielineState : MTLRenderPipelineState!
    private var viewPortSize : vector_uint2 = vector_uint2(x: 0, y: 0)
初始化
init(view: MTKView) {
        super.init()
        _device = view.device
        
        //1. 通过device创建commandQueue
        commandQueue = _device?.makeCommandQueue()
        
        //2. 加载metal文件
        //2.1 makeDefaultLibrary:加载项目中所有.metal文件,当然也可以使用其他API来指定metal文件
        let library = _device?.makeDefaultLibrary()
        //2.2 从库中加载顶点函数、片元函数
        let vertexShader = library?.makeFunction(name: "vertexShader")
        let fragShader = library?.makeFunction(name: "fragmentShader")
        
        //3. 创建渲染管道描述符
        //3.1
        let pielineDes = MTLRenderPipelineDescriptor()
        //3.2 管道名称:可用于调试
        pielineDes.label = "MyMTLRenderPipelineDescriptor"
        //3.2 可编程函数,用于处理渲染过程中每个顶点、片元
        pielineDes.vertexFunction = vertexShader
        pielineDes.fragmentFunction = fragShader
        //3.3 确定渲染管线中颜色附着点0的颜色组件;使用当前view颜色组件
        pielineDes.colorAttachments[0].pixelFormat = view.colorPixelFormat
        
        //4 创建渲染管线状态
        do {
            try pielineState = _device?.makeRenderPipelineState(descriptor: pielineDes)
        } catch {
            //如果我们没有正确设置管道描述符,则管道状态创建可能失败
            print("pielineState failed \(error)")
        }  
    }
Delegate-drawSize
func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
        viewPortSize.x = uint(size.width)
        viewPortSize.y = uint(size.height)
    }
Delegate-draw
func draw(in view: MTKView) {
        //设置view的clearColor
        view.clearColor = MTLClearColor(red: 0, green: 0, blue: 0, alpha: 1.0)
        
        //5. 为每一次渲染创建一个新的命令缓冲区
        let commandBuffer = commandQueue?.makeCommandBuffer()
        commandBuffer?.label = "MyCommandBuffer"
        
        //6. 用于保存渲染过程中的一组结果,渲染命令编码器描述符
        if let des = view.currentRenderPassDescriptor {
            //7. 创建渲染命令编码器,通过它来进行渲染的配置
            let encoder = commandBuffer?.makeRenderCommandEncoder(descriptor: des)
            encoder?.label = "MyCommandEncoder"
            
            //8. 设置视口
            encoder?.setViewport(MTLViewport(originX: 0, originY: 0,
                                             width: Double(viewPortSize.x),
                                             height: Double(viewPortSize.y),
                                             znear: -1.0, zfar: 1.0))
            //9. 设置当前渲染管道状态对象
            encoder?.setRenderPipelineState(pielineState)
            
            //10. 载入顶点数据
            //通过VertexInputIndexVertices将数据传递到顶点函数的对应buffer中
            encoder?.setVertexBytes(triangleVertices,
                                    length: triangleVertices.count * MemoryLayout<HRVertex>.size,
                                    index: Int(VertexInputIndexVertices.rawValue))
            
            encoder?.setVertexBytes(&viewPortSize,
                                    length: MemoryLayout<vector_uint2>.size,
                                    index: Int(VertexInputIndexViewPortSize.rawValue))
            
            
            
            //11. 绘制动作
            /*
                type: 设置图元链接方式
                    case point = 0
                    case line = 1
                    case lineStrip = 2  //线环
                    case triangle = 3   //三角形
                    case triangleStrip = 4  //三角形扇
             */
            encoder?.drawPrimitives(type: .triangle,
                                    vertexStart: 0,
                                    vertexCount: triangleVertices.count)
            
            //12. 结束编码
            encoder?.endEncoding()
            
            //13. 锁定缓存区, 等待缓冲区处理完成后绘制
            if let currentDrawable = view.currentDrawable{
                commandBuffer?.present(currentDrawable)
            }
        }
        
        //14. 将命令缓存区提交给GPU
        commandBuffer?.commit()
    }
Buffer方式导入顶点数据

上方代码使用的是直接导入的方式将顶点数据导入顶点函数。当然还有其他方式,比如使用Buffer的方式来导入。

init(view: MTKView) {
...
// 将数据放入buffer中,但是buffer是有大小上限:4KB
        vertexBuffer = _device?.makeBuffer(bytes: triangleVertices,
                                         length: triangleVertices.count * MemoryLayout<HRVertex>.size,
                                         options: .storageModeShared)
}

func draw(in view: MTKView) {
// 通过buffer的方式载入顶点数据
    encoder?.setVertexBuffer(vertexBuffer,
                                     offset: 0,
                                     index: Int(VertexInputIndexVertices.rawValue))
}
  • 由于buffer最大可存储4KB的数据,所以不适合大量数据的导入

4,渲染类的使用

override func viewDidLoad() {
        super.viewDidLoad()
        
        //1.获取MTKView
        if let vv = self.view as? MTKView{
            _view = vv
            //2.创建设备(GPU)
            _view?.device = MTLCreateSystemDefaultDevice()
            
            //3.render工具类创建
            _render = HrRender(view: _view)
            
            //4.mtkview的代理设置
            _view?.delegate = _render
            
            //5.初始化视图大小
            //drawableSize当前view的可视区域
            _render?.mtkView(vv, drawableSizeWillChange: _view.drawableSize)
        }
    }

demo-github地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,734评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,931评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,133评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,532评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,585评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,462评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,262评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,153评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,587评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,792评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,919评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,635评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,237评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,855评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,983评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,048评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,864评论 2 354