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

2020-09-01  本文已影响0人  Henry________

相比于上一篇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;
}
fragment函数
fragment float4 fragmentShader(RasterizerData in [[stage_in]]) {
    //返回该像素点的色值
    return in.color;
}

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))
}

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地址

上一篇下一篇

猜你喜欢

热点阅读