Metal與圖形渲染七:藍(lán)線挑戰(zhàn)

零. 前言

藍(lán)線挑戰(zhàn)寺董,曾經(jīng)一度風(fēng)靡各大短視頻平臺(tái)的一個(gè)玩法,不乏有大神利用這個(gè)玩法產(chǎn)生了一系列的神作豁遭,更不乏失敗踩雷的各種捧腹之作拴还,今天我們來用Metal實(shí)現(xiàn)一下這個(gè)可玩性很高的挑戰(zhàn)吧~

一. 原理概述

藍(lán)線挑戰(zhàn)的特點(diǎn)是:處于藍(lán)線掃過的地方膝宁,取的是之前渲染過的內(nèi)容鸦难;處于藍(lán)線未掃過的地方,取的是當(dāng)前攝像頭的內(nèi)容员淫,而處于藍(lán)線的范圍合蔽,取的自然是藍(lán)線的色值,于是乎我們得到一條渲染鏈:

攝像頭獲取到CVPixelBuffer后介返,讓MovieReader處理生成紋理拴事,BlueLineFilter先根據(jù)攝像頭的紋理和自己上一幀處理過的輸出紋理渲染進(jìn)行,然后讓DrawBlueLineFilter進(jìn)行藍(lán)線的繪制圣蝎,最終渲染到RenderView上面去刃宵。

核心就是:處理好上一幀之后的紋理要存儲(chǔ)好,和當(dāng)前攝像頭的紋理進(jìn)行渲染徘公,就可以得到合起來的內(nèi)容啦~

二. BlueLineFilter

該Filter核心是如何存儲(chǔ)之前渲染好的內(nèi)容和獲取當(dāng)前的內(nèi)容進(jìn)行渲染牲证,其Shader如下:

fragment float4 blueLineFragment(TwoInputVertexIO input [[ stage_in ]],
                                 texture2d<float> cameraTexture [[texture(0)]],
                                 texture2d<float> screenShotTexture [[texture(1)]],
                                 constant float &offset [[ buffer(0) ]],
                                 constant bool &isVertical [[ buffer(1) ]])
{
    constexpr sampler quadSampler;
    float4 cameraColor = cameraTexture.sample(quadSampler, input.textureCoordinate);
    float4 screenShotColor = screenShotTexture.sample(quadSampler, input.textureCoordinate2);
    
    float coor = isVertical ? input.position.y : input.position.x;
    
    if (coor < offset) {
        return screenShotColor;
    } else {
        return cameraColor;
    }
}

代碼邏輯非常簡(jiǎn)單,Offset代表當(dāng)前藍(lán)線處于的位置关面,如果處于藍(lán)線之前坦袍,則取上一幀的輸出十厢,如果處于藍(lán)線之后,則取當(dāng)前攝像頭的輸出捂齐。水平移動(dòng)取x蛮放,垂直移動(dòng)取y。

來看看怎么獲取上一幀的紋理的:

- (instancetype)initWithRenderContext:(HobenMetalRenderContext *)renderContext {
    return [super initWithVertexName:@"twoInputVertex" fragmentName:@"blueLineFragment" numberOfInputs:2 renderContext:renderContext];
}

- (void)newTextureAvailable:(id<MTLTexture>)texture index:(NSInteger)index commandBuffer:(id<MTLCommandBuffer>)commandBuffer {
    [super newTextureAvailable:texture index:index commandBuffer:commandBuffer];
    
    if (!_lastTexture) {
        _lastTexture = texture;
    }

    [super newTextureAvailable:_lastTexture index:1 commandBuffer:commandBuffer];
}

- (void)renderToTextureWithVertices:(NSArray *)vertices textureCoordinates:(NSArray *)textureCoordinates {
    for (HobenMetalTexture *inputTexture in _inputTextures) {
        inputTexture.textureCoordinates = textureCoordinates;
    }
    float offset = _percent * _lastTexture.height;
    id <MTLBuffer> offsetBuffer = [_renderContext.device newBufferWithBytes:&offset length:sizeof(float) options:MTLResourceStorageModeShared];
    bool isVertical = _isVertical;
    id <MTLBuffer> isVerticalBuffer = [_renderContext.device newBufferWithBytes:&isVertical length:sizeof(bool) options:MTLResourceStorageModeShared];
    
    [_renderContext renderQuad:_pipelineState inputTextures:_inputTextures imageVertices:vertices vertexBuffers:nil fragmentBuffers:@[offsetBuffer, isVerticalBuffer] outputTexture:_outputTexture commandBuffer:_filterCommandBuffer];
    
    [self transmitTextureToAllTargets:_outputTexture commandBuffer:_filterCommandBuffer];
    
    self.lastTexture = _outputTexture;
}

同樣比較清晰奠宜,先聲明這是個(gè)雙輸入Filter包颁,然后根據(jù)存儲(chǔ)上一幀的outputTexture作為輸入,和當(dāng)前攝像頭的紋理進(jìn)行渲染即可挎塌,percent和isVertical都是由外部定義好傳進(jìn)來的徘六。

三. DrawBlueLineFilter

該Filter主要作用是進(jìn)行藍(lán)線的繪制,Shader如下:

constant float4 blueLineColor = float4(0, 1, 1, 1);

constant float blueLineSize = 5;

fragment float4 drawBlueLineFragment(SingleInputVertexIO input [[ stage_in ]],
                                     texture2d<float> inputTexture [[texture(0)]],
                                     constant float &offset [[ buffer(0) ]],
                                     constant bool &isVertical [[ buffer(1) ]])
{
    constexpr sampler quadSampler;
    float4 inputTextureColor = inputTexture.sample(quadSampler, input.textureCoordinate);
    
    float coor = isVertical ? input.position.y : input.position.x;
    
    if (coor < offset || coor > offset + blueLineSize) {
        return inputTextureColor;
    } else {
        return blueLineColor;
    }
}

原理其實(shí)和上面差不多榴都,到底是取藍(lán)線顏色還是取攝像頭顏色待锈,邏輯很清晰了,這里就不講解了~

四. 外部調(diào)用

其實(shí)就是加個(gè)定時(shí)器和藍(lán)線移動(dòng)開關(guān)嘴高,也沒啥好說的竿音。

- (void)startTimer {
    _percent = 0;
    [self stopTimer];
    __weak typeof(self) weakSelf = self;
    self.timer = [NSTimer timerWithTimeInterval:0.015 repeats:YES block:^(NSTimer * _Nonnull timer) {
        weakSelf.percent += 0.001;
    }];
    
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}

- (void)stopTimer {
    if (self.timer) {
        [self.timer invalidate];
        self.timer = nil;
    }
}

- (void)setPercent:(CGFloat)percent {
    _percent = percent;
    
    if (percent > 1) {
        [self stopRecord];
        return;
    }
    
    self.blueLineFilter.percent = percent;
    self.drawBlueLineFilter.percent = percent;
}

五. 總結(jié)

這是我做過最好玩的一個(gè)Demo,趣味性非常高拴驮,能搞笑也能秀操作春瞬。這個(gè)項(xiàng)目主要的難點(diǎn)是如何獲取到上一幀的紋理,但因?yàn)樽约悍庋b了一個(gè)可復(fù)用性較高的MetalKit套啤,所以也不算特別難宽气,越來越感覺到鏈?zhǔn)戒秩镜暮糜昧藒

參考:使用OpenGL挑戰(zhàn)抖音藍(lán)線特效

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市潜沦,隨后出現(xiàn)的幾起案子萄涯,更是在濱河造成了極大的恐慌,老刑警劉巖唆鸡,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件涝影,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡争占,警方通過查閱死者的電腦和手機(jī)燃逻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來臂痕,“玉大人伯襟,你說我怎么就攤上這事∥胀” “怎么了逗旁?”我有些...
    開封第一講書人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我片效,道長(zhǎng)红伦,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任淀衣,我火速辦了婚禮昙读,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘膨桥。我一直安慰自己蛮浑,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開白布只嚣。 她就那樣靜靜地躺著沮稚,像睡著了一般。 火紅的嫁衣襯著肌膚如雪册舞。 梳的紋絲不亂的頭發(fā)上蕴掏,一...
    開封第一講書人閱讀 51,554評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音调鲸,去河邊找鬼盛杰。 笑死,一個(gè)胖子當(dāng)著我的面吹牛藐石,可吹牛的內(nèi)容都是我干的即供。 我是一名探鬼主播,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼于微,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼逗嫡!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起株依,我...
    開封第一講書人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤祸穷,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后勺三,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡需曾,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年吗坚,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片呆万。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡商源,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出谋减,到底是詐尸還是另有隱情牡彻,我是刑警寧澤,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布,位于F島的核電站庄吼,受9級(jí)特大地震影響缎除,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜总寻,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一器罐、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧渐行,春花似錦轰坊、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至蕴忆,卻和暖如春颤芬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背孽文。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工驻襟, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人芋哭。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓沉衣,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親减牺。 傳聞我的和親對(duì)象是個(gè)殘疾皇子豌习,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容