使用AVFoundation可以拍摄视频,Avplayer播放视频。如果使用Metal取处理视频的话需要把视频的每一帧去处理然后显示,
需要用到CMSampleBuffer相关处理,本篇滤镜使用了Metal的内置滤镜MetalPerformanceShaders,MPS提供了许多常用的滤镜,具体可以参考apple官方文档
一、使用Metal处理实时拍摄视频
class ViewController: UIViewController {
//按钮
var captureButton:UIButton!
var recodButton:UIButton!
var session : AVCaptureSession = AVCaptureSession()
var queue = DispatchQueue(label: "quque")
var input: AVCaptureDeviceInput?
lazy var previewLayer = AVCaptureVideoPreviewLayer(session: self.session)
lazy var recordOutput = AVCaptureMovieFileOutput()
//Metal相关
var device :MTLDevice!
var mtkView : MTKView!
var texture : MTLTexture?
var tetureCache : CVMetalTextureCache?
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
captureButton = UIButton(frame: CGRect(x: 10, y: view.bounds.size.height - 60, width: 150, height: 50))
captureButton.backgroundColor = .gray
captureButton.setTitle("start capture", for: .normal)
captureButton.addTarget(self, action: #selector(capture(btn:)), for: .touchUpInside)
view.addSubview(captureButton)
recodButton = UIButton(frame: CGRect(x: view.bounds.size.width - 160, y: view.bounds.size.height - 60, width: 150, height: 50))
recodButton.backgroundColor = .gray
recodButton.setTitle("paly movie", for: .normal)
recodButton.addTarget(self, action: #selector(recordAction(btn:)), for: .touchUpInside)
view.addSubview(recodButton)
}
func setMetalConfig() {
guard let device1 = MTLCreateSystemDefaultDevice() else{
return
}
self.device = device1
mtkView = MTKView(frame: view.bounds, device: device)
mtkView.delegate = self
mtkView.framebufferOnly = false
//创建纹理缓存区
CVMetalTextureCacheCreate(nil, nil, device1, nil, &tetureCache)
}
@objc func recordAction(btn:UIButton){
btn.isSelected = !btn.isSelected
if session.isRunning {
if btn.isSelected {
btn.setTitle("stop record", for: .normal)
if !session.isRunning{
session.startRunning()
}
if session.canAddOutput(recordOutput){
session.addOutput(recordOutput)
}
// recordOutput.
let connection = recordOutput.connection(with: .video)
connection?.preferredVideoStabilizationMode = .auto
guard let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first else { return }
let url = URL(fileURLWithPath: "\(path)/test.mp4")
recordOutput.startRecording(to: url, recordingDelegate: self)
}else{
btn.setTitle("start record", for: .normal)
recordOutput.stopRecording()
}
}else{
// btn.setTitle("paly movie", for: .normal)
let moVC = MovieViewController()
self.navigationController?.pushViewController(moVC, animated: true)
}
}
@objc func capture(btn:UIButton){
btn.isSelected = !btn.isSelected
if btn.isSelected {
// recodButton.isHidden = false
recodButton.setTitle("start record", for: .normal)
btn.setTitle("stop capture", for: UIControl.State.normal)
guard let device = getCamera(postion: .back) else{
return
}
guard let input = try? AVCaptureDeviceInput(device: device) else{
return
}
self.input = input
if session.canAddInput(input) {
session.addInput(input)
}
let output = AVCaptureVideoDataOutput()
output.setSampleBufferDelegate(self, queue: queue)
if session.canAddOutput(output){
session.addOutput(output)
}
//这里设置格式为BGRA,而不用YUV的颜色空间,避免使用Shader转换
//注意:这里必须和后面CVMetalTextureCacheCreateTextureFromImage 保存图像像素存储格式保持一致.否则视频会出现异常现象.
output.videoSettings = [String(kCVPixelBufferPixelFormatTypeKey) :NSNumber(value: kCVPixelFormatType_32BGRA) ]
let connection: AVCaptureConnection = output.connection(with: .video)!
connection.videoOrientation = .portrait
// previewLayer.frame = view.bounds
// view.layer.insertSublayer(previewLayer, at: 0)
setMetalConfig()
view.insertSubview(mtkView, at: 0)
session.startRunning()
}else{
// recodButton.isHidden = true
btn.setTitle("start capture", for: .normal)
if recordOutput.isRecording {
recordOutput.stopRecording()
}
recodButton.isSelected = false
recodButton.setTitle("play movie", for: .normal)
session.stopRunning()
// previewLayer.removeFromSuperlayer()
mtkView.removeFromSuperview()
}
}
//获取相机设备
func getCamera(postion: AVCaptureDevice.Position) -> AVCaptureDevice? {
var devices = [AVCaptureDevice]()
if #available(iOS 10.0, *) {
let discoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: [AVCaptureDevice.DeviceType.builtInWideAngleCamera], mediaType: AVMediaType.video, position: AVCaptureDevice.Position.unspecified)
devices = discoverySession.devices
} else {
devices = AVCaptureDevice.devices(for: AVMediaType.video)
}
for device in devices {
if device.position == postion {
return device
}
}
return nil
}
//切换摄像头
func swapFrontAndBackCameras() {
if let input = input {
var newDevice: AVCaptureDevice?
if input.device.position == .front {
newDevice = getCamera(postion: AVCaptureDevice.Position.back)
} else {
newDevice = getCamera(postion: AVCaptureDevice.Position.front)
}
if let new = newDevice {
do{
let newInput = try AVCaptureDeviceInput(device: new)
session.beginConfiguration()
session.removeInput(input)
session.addInput(newInput)
self.input = newInput
session.commitConfiguration()
}
catch let error as NSError {
print("AVCaptureDeviceInput(): \(error)")
}
}
}
}
//设置横竖屏问题
func setupVideoPreviewLayerOrientation() {
if let connection = previewLayer.connection, connection.isVideoOrientationSupported {
if #available(iOS 13.0, *) {
if let orientation = UIApplication.shared.windows.first?.windowScene?.interfaceOrientation{
switch orientation {
case .portrait:
connection.videoOrientation = .portrait
case .landscapeLeft:
connection.videoOrientation = .landscapeLeft
case .landscapeRight:
connection.videoOrientation = .landscapeRight
case .portraitUpsideDown:
connection.videoOrientation = .portraitUpsideDown
default:
connection.videoOrientation = .portrait
}
}
}else{
switch UIApplication.shared.statusBarOrientation {
case .portrait:
connection.videoOrientation = .portrait
case .landscapeRight:
connection.videoOrientation = .landscapeRight
case .landscapeLeft:
connection.videoOrientation = .landscapeLeft
case .portraitUpsideDown:
connection.videoOrientation = .portraitUpsideDown
default:
connection.videoOrientation = .portrait
}
}
}
}
}
extension ViewController : AVCaptureVideoDataOutputSampleBufferDelegate,AVCaptureFileOutputRecordingDelegate,MTKViewDelegate {
//mtk
func draw(in view: MTKView) {
guard let queue = device.makeCommandQueue() else { return }
guard let buffer = queue.makeCommandBuffer() else { return }
// guard let descriptor = mtkView.currentRenderPassDescriptor else{return}
// guard let encode = buffer.makeRenderCommandEncoder(descriptor: descriptor) else {
// return
// }
//metal有许多内置滤镜 MetalPerformanceShaders
let blurFilter = MPSImageGaussianBlur.init(device: device, sigma: 10)
guard let texture = self.texture else {
return
}
blurFilter.encode(commandBuffer: buffer, sourceTexture: texture, destinationTexture: view.currentDrawable!.texture)
buffer.present(view.currentDrawable!)
buffer.commit()
self.texture = nil
}
func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
}
//录制完成
func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) {
}
//采集结果
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }
// imageBuffer.attachments[0].
var metalTexture:CVMetalTexture?
let status = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault,
self.tetureCache!,
imageBuffer,
nil,
MTLPixelFormat.bgra8Unorm,
CVPixelBufferGetWidth(imageBuffer),
CVPixelBufferGetHeight(imageBuffer),
0,
&metalTexture)
if status == kCVReturnSuccess {
mtkView.drawableSize = CGSize(width: CVPixelBufferGetWidth(imageBuffer), height: CVPixelBufferGetHeight(imageBuffer))
self.texture = CVMetalTextureGetTexture(metalTexture!)
}
}
func captureOutput(_ output: AVCaptureOutput, didDrop sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
}
}
二、处理已有视频
import UIKit
import AVFoundation
import MetalKit
import MetalPerformanceShaders
struct ConvertMatrix {
var matrix :float3x3
var verctor :SIMD3<Float>
}
class MovieViewController: UIViewController {
var device :MTLDevice!
var mtkView : MTKView!
var reader: DQAssetReader?
var texture : MTLTexture?
var textureUV:MTLTexture?
var tetureCache : CVMetalTextureCache?
var state : MTLRenderPipelineState?
var commendQueue: MTLCommandQueue?
var vertexbuffer :MTLBuffer?
var cmatrixBuffer :MTLBuffer?
var useYUV = true
var timeRange : CMTimeRange?
var pauseButton:UIButton!
override func viewDidLoad() {
super.viewDidLoad()
self.title = "movie"
self.view.backgroundColor = .white
let path = Bundle.main.path(forResource: "123", ofType: "mp4")
let url1 = URL(fileURLWithPath: path!)
reader = DQAssetReader(url: url1,valueYUV: useYUV)
reader?.timeRange = CMTimeRange(start: CMTime(value: 2, timescale: 1, flags: CMTimeFlags(rawValue: 1), epoch: 0), duration: CMTime(value: 0, timescale: 0, flags: CMTimeFlags(rawValue: 5), epoch: 0))
setMetalConfig()
vertexData()
yuvToRGBmatrix()
pauseButton = UIButton(frame: CGRect(x: 0, y: view.frame.size.height - 100, width: 100, height: 50))
pauseButton.center.x = view.center.x
pauseButton.setTitle("暂停", for:.normal)
pauseButton.setTitle("继续", for:.selected)
pauseButton.backgroundColor = .gray
view.addSubview(pauseButton)
pauseButton.addTarget(self, action: #selector(pauseAction(btn:)), for: .touchUpInside)
}
@objc func pauseAction(btn:UIButton){
btn.isSelected = !btn.isSelected
if !btn.isSelected {
if reader?.readBuffer() == nil {
reader?.setUpAsset()
pauseButton.setTitle("继续", for:.selected)
}
}
}
func setMetalConfig() {
guard let device1 = MTLCreateSystemDefaultDevice() else{
return
}
self.device = device1
mtkView = MTKView(frame: view.bounds, device: device)
mtkView.delegate = self
mtkView.framebufferOnly = false
//创建纹理缓存区
CVMetalTextureCacheCreate(nil, nil, device1, nil, &tetureCache)
view.addSubview(mtkView)
let library = device.makeDefaultLibrary()
let verFunc = library?.makeFunction(name: "vertexShader")
let fragFunc = library?.makeFunction(name: "samplingShader")
let descriptor = MTLRenderPipelineDescriptor()
descriptor.fragmentFunction = fragFunc
descriptor.vertexFunction = verFunc
descriptor.colorAttachments[0].pixelFormat = mtkView.colorPixelFormat
state = try? device.makeRenderPipelineState(descriptor: descriptor)
commendQueue = device.makeCommandQueue()
}
func vertexData() {
var vertex:[Float] = [
1.0, -1.0, 0.0, 1.0, 1.0, 1.0,1.0,1.0,
-1.0, -1.0, 0.0, 1.0, 0.0, 1.0,1.0,1.0,
-1.0, 1.0, 0.0, 1.0, 0.0, 0.0,1.0,1.0,
1.0, -1.0, 0.0, 1.0, 1.0, 1.0,1.0,1.0,
-1.0, 1.0, 0.0, 1.0, 0.0, 0.0,1.0,1.0,
1.0, 1.0, 0.0, 1.0, 1.0, 0.0,1.0,1.0
]
vertexbuffer = device.makeBuffer(bytes: &vertex, length: MemoryLayout<Float>.size * vertex.count, options: MTLResourceOptions.storageModeShared)
}
func changeVertex(sampleBuffer:CMSampleBuffer) {
var vertexs:[Float] = [
1.0, -1.0, 0.0, 1.0, 1.0, 1.0,1.0,1.0,
-1.0, -1.0, 0.0, 1.0, 0.0, 1.0,1.0,1.0,
-1.0, 1.0, 0.0, 1.0, 0.0, 0.0,1.0,1.0,
1.0, -1.0, 0.0, 1.0, 1.0, 1.0,1.0,1.0,
-1.0, 1.0, 0.0, 1.0, 0.0, 0.0,1.0,1.0,
1.0, 1.0, 0.0, 1.0, 1.0, 0.0,1.0,1.0
]
guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
return
}
let width = CVPixelBufferGetWidth(imageBuffer)
let height = CVPixelBufferGetHeight(imageBuffer)
let scaleF = CGFloat(view.frame.height)/CGFloat(view.frame.width)
let scaleI = CGFloat(height)/CGFloat(width)
let imageScale = scaleF>scaleI ? (1,scaleI/scaleF) : (scaleF/scaleI,1)
for (i,v) in vertexs.enumerated(){
if i % 8 == 0 {
vertexs[i] = v * Float(imageScale.0)
}
if i % 8 == 1{
vertexs[i] = v * Float(imageScale.1)
}
}
vertexbuffer = device.makeBuffer(bytes: vertexs, length: MemoryLayout<Float>.size * vertexs.count, options: MTLResourceOptions.storageModeShared)
}
func yuvToRGBmatrix() {
/*
YUV与RGB相互转化公式
传输时使用YUV节省空间大小
4:4:4 YUV全部取值。 不节省空间
4:2:2 U/V隔一个取一个。 节省1/3
4:2:0 第一行取U,第二行取V,还是隔一个取一个 节省1/2
Y = 0.299 * R + 0.587 * G + 0.114 * B
U = -0.174 * R - 0.289 * G + 0.436 * B
V = 0.615 * R - 0.515 * G - 0.100 * B
R = Y + 1.14 V
G = Y - 0.390 * U - 0.58 * V
B = Y + 2.03 * U
*/
//1.转化矩阵
// BT.601, which is the standard for SDTV.
let kColorConversion601DefaultMatrix = float3x3(
SIMD3<Float>(1.164,1.164, 1.164),
SIMD3<Float>(0.0, -0.392, 2.017),
SIMD3<Float>(1.596, -0.813, 0.0))
// BT.601 full range
let kColorConversion601FullRangeMatrix = float3x3(
SIMD3<Float>(1.0, 1.0, 1.0),
SIMD3<Float>(0.0, -0.343, 1.765),
SIMD3<Float>(1.4, -0.711, 0.0))
// BT.709, which is the standard for HDTV.
let kColorConversion709DefaultMatrix = float3x3(
SIMD3<Float>(1.164, 1.164, 1.164),
SIMD3<Float>(0.0, -0.213, 2.112),
SIMD3<Float>(1.793, -0.533, 0.0))
//
let offset = SIMD3<Float>(-(16.0/255.0), -0.5, -0.5)
var cMatrix = ConvertMatrix(matrix: kColorConversion601FullRangeMatrix, verctor: offset)
self.cmatrixBuffer = device.makeBuffer(bytes: &cMatrix, length: MemoryLayout<ConvertMatrix>.size, options: .storageModeShared)
}
}
extension MovieViewController:MTKViewDelegate {
func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
}
func draw(in view: MTKView) {
if pauseButton.isSelected {
return
}
guard let commandBuffer = commendQueue?.makeCommandBuffer() else {
return
}
//texture
guard let sample = self.reader?.readBuffer() else {
pauseButton.isSelected = true
pauseButton.setTitle("重播", for: UIControl.State.selected)
return
}
//encode
guard let passDescriptor = view.currentRenderPassDescriptor else{return}
passDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0.3, 0.1, 0.4, 1)
guard let encode = commandBuffer.makeRenderCommandEncoder(descriptor: passDescriptor) else{return}
guard let pipeState = self.state else {return}
encode.setRenderPipelineState(pipeState)
encode.setViewport(MTLViewport(originX: 0, originY: 0, width: Double(view.drawableSize.width), height: Double(view.drawableSize.height), znear: -1, zfar: 1))
changeVertex(sampleBuffer: sample)
encode.setVertexBuffer(vertexbuffer, offset: 0, index: 0)
encode.setFragmentBuffer(cmatrixBuffer, offset: 0, index: 0)
setTextureWithEncoder(encoder: encode,sampleBuffer: sample,yuv: useYUV)
if let blendTex = ImageTool.setUpImageTexture(imageName: "image.jpg", device: device) {
encode.setFragmentTexture(blendTex, index: 2)
}
encode.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 6)
encode.endEncoding()
commandBuffer.present(view.currentDrawable!)
commandBuffer.commit()
self.texture = nil
}
func setTextureWithEncoder(encoder:MTLRenderCommandEncoder,sampleBuffer:CMSampleBuffer,yuv:Bool = false) {
guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
return
}
func settexture(index:Int){
var pixelFormat:MTLPixelFormat = .bgra8Unorm
if index == -1{
pixelFormat = .bgra8Unorm
}else if index == 0{
pixelFormat = .r8Unorm
}else if index == 1{
pixelFormat = .rg8Unorm
}
var metalTexture:CVImageBuffer?
let width = CVPixelBufferGetWidthOfPlane(imageBuffer, index == -1 ? 0 : index)
let hieght = CVPixelBufferGetHeightOfPlane(imageBuffer, index == -1 ? 0 : index)
let status = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault,
self.tetureCache!,
imageBuffer,
nil,
pixelFormat,
width,
hieght,
index == -1 ? 0 : index,
&metalTexture)
if status == kCVReturnSuccess{
if index == 1 {
self.textureUV = CVMetalTextureGetTexture(metalTexture!)
encoder.setFragmentTexture(self.textureUV, index: 1)
}else{
self.texture = CVMetalTextureGetTexture(metalTexture!)
encoder.setFragmentTexture(self.texture, index: 0)
}
}
}
if yuv {
settexture(index: 0)
settexture(index: 1)
}else{
settexture(index: -1)
}
}
}
三、逐帧获取视频文件每帧的工具类
class DQAssetReader: NSObject {
var readerVideoTrackOutput:AVAssetReaderTrackOutput?
var assetReader:AVAssetReader!
var lockObjc = NSObject()
var videoUrl:URL
var inputAsset :AVAsset!
var YUV : Bool = false
var timeRange:CMTimeRange?
var loop: Bool = false
init(url:URL,valueYUV:Bool = false) {
videoUrl = url
YUV = valueYUV
super.init()
setUpAsset()
}
func setUpAsset(startRead:Bool = true) {
//创建AVUrlAsset,用于从本地/远程URL初始化资源
//AVURLAssetPreferPreciseDurationAndTimingKey 默认为NO,YES表示提供精确的时长
inputAsset = AVURLAsset(url: videoUrl, options: [AVURLAssetPreferPreciseDurationAndTimingKey:true])
//对资源所需的键执行标准的异步载入操作,这样就可以访问资源的tracks属性时,就不会受到阻碍.
inputAsset.loadValuesAsynchronously(forKeys: ["tracks"]) {[weak self] in
guard let `self` = self else{
return
}
//开辟子线程并发队列异步函数来处理读取的inputAsset
DispatchQueue.global().async {[weak self] in
guard let `self` = self else{
return
}
var error: NSError?
let tracksStatus = self.inputAsset.statusOfValue(forKey: "tracks", error: &error)
//如果状态不等于成功加载,则返回并打印错误信息
if tracksStatus != .loaded{
print(error?.description as Any)
return
}
self.processAsset(asset: self.inputAsset,startRead: startRead)
}
}
}
func processAsset(asset:AVAsset,startRead:Bool = true) {
//加锁
objc_sync_enter(lockObjc)
//创建AVAssetReader
guard let assetReader1 = try? AVAssetReader(asset: asset) else {
return
}
assetReader = assetReader1
//
/*
2.kCVPixelBufferPixelFormatTypeKey 像素格式.
kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange : 420v(YUV)
kCVPixelFormatType_32BGRA : iOS在内部进行YUV至BGRA格式转换
3. 设置readerVideoTrackOutput
assetReaderTrackOutputWithTrack:(AVAssetTrack *)track outputSettings:(nullable NSDictionary<NSString *, id> *)outputSettings
参数1: 表示读取资源中什么信息
参数2: 视频参数
*/
let pixelFormat = YUV ? kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange : kCVPixelFormatType_32BGRA
readerVideoTrackOutput = AVAssetReaderTrackOutput(track: asset.tracks(withMediaType: .video).first!, outputSettings:[String(kCVPixelBufferPixelFormatTypeKey) :NSNumber(value: pixelFormat)])
//alwaysCopiesSampleData : 表示缓存区的数据输出之前是否会被复制.YES:输出总是从缓存区提供复制的数据,你可以自由的修改这些缓存区数据The default value is YES.
readerVideoTrackOutput?.alwaysCopiesSampleData = false
if assetReader.canAdd(readerVideoTrackOutput!){
assetReader.add(readerVideoTrackOutput!)
}
//开始读取
if startRead {
if assetReader.startReading() == false {
print("reading file error")
}
}
//解锁
objc_sync_exit(lockObjc)
}
//读取每一帧
func readBuffer() -> CMSampleBuffer? {
objc_sync_enter(lockObjc)
var sampleBuffer:CMSampleBuffer?
if let readerTrackout = self.readerVideoTrackOutput {
sampleBuffer = readerTrackout.copyNextSampleBuffer()
}
//判断assetReader 并且status 是已经完成读取 则重新清空readerVideoTrackOutput/assetReader.并重新初始化它们
if assetReader != nil,assetReader.status == .completed {
readerVideoTrackOutput = nil
assetReader = nil
if loop {
self.setUpAsset()
}
}
//时间
// print(sampleBuffer?.presentationTimeStamp.value as Any)
objc_sync_exit(lockObjc)
return sampleBuffer
}
}
四、Metal文件代码
#include <metal_stdlib>
using namespace metal;
//顶点数据结构
typedef struct
{
//顶点坐标(x,y,z,w)
vector_float4 position;
//纹理坐标(s,t)
vector_float2 textureCoordinate;
} CCVertex;
//转换矩阵
typedef struct {
//三维矩阵
float3x3 matrix;
//偏移量
vector_float3 offset;
} CCConvertMatrix;
//结构体(用于顶点函数输出/片元函数输入)
typedef struct
{
float4 clipSpacePosition [[position]]; // position的修饰符表示这个是顶点
float2 textureCoordinate; // 纹理坐标
} RasterizerData;
//RasterizerData 返回数据类型->片元函数
// vertex_id是顶点shader每次处理的index,用于定位当前的顶点
// buffer表明是缓存数据,0是索引
vertex RasterizerData
vertexShader(uint vertexID [[ vertex_id ]],
constant CCVertex *vertexArray [[ buffer(0) ]])
{
RasterizerData out;
//顶点坐标
out.clipSpacePosition = vertexArray[vertexID].position;
//纹理坐标
out.textureCoordinate = vertexArray[vertexID].textureCoordinate;
return out;
}
//YUV->RGB 参考学习链接: https://mp.weixin.qq.com/s/KKfkS5QpwPAdYcEwFAN9VA
// stage_in表示这个数据来自光栅化。(光栅化是顶点处理之后的步骤,业务层无法修改)
// texture表明是纹理数据,CCFragmentTextureIndexTextureY是索引
// texture表明是纹理数据,CCFragmentTextureIndexTextureUV是索引
// buffer表明是缓存数据, CCFragmentInputIndexMatrix是索引
fragment float4
samplingShader(RasterizerData input [[stage_in]],
texture2d<float> textureY [[ texture(0) ]],
texture2d<float> textureUV [[ texture(1) ]],
texture2d<float> textureBlend [[ texture(2) ]],
constant CCConvertMatrix *convertMatrix [[ buffer(0) ]]
)
{
//1.获取纹理采样器
constexpr sampler textureSampler (mag_filter::linear,
min_filter::linear);
/*
2. 读取YUV 颜色值
textureY.sample(textureSampler, input.textureCoordinate).r
从textureY中的纹理采集器中读取,纹理坐标对应上的R值.(Y)
textureUV.sample(textureSampler, input.textureCoordinate).rg
从textureUV中的纹理采集器中读取,纹理坐标对应上的RG值.(UV)
*/
float3 yuv = float3(textureY.sample(textureSampler, input.textureCoordinate).r,
textureUV.sample(textureSampler, input.textureCoordinate).rg);
float Y = textureY.sample(textureSampler, input.textureCoordinate).r;
float3 rgb1 = float3(Y,Y,Y);//黑白的
//3.将YUV 转化为 RGB值.convertMatrix->matrix * (YUV + convertMatrix->offset)
float3 rgb = convertMatrix->matrix * (yuv + convertMatrix->offset);
//混合滤镜颜色
float4 blend = textureBlend.sample(textureSampler, input.textureCoordinate);
return float4(rgb,1.0) * 0.4 + blend * 0.6;
//4.返回颜色值(RGBA)
// return float4(rgb, 1.0);
}