Vulkan移植GPUImage(五)從P到Z的濾鏡

天天不在發表於2021-05-20

aoce_vulkan_extra把GPUImage裡從P到Z的大部分濾鏡用vulkan的ComputeShader實現了,也就是最後一部分的移植,整個過程相對前面來說比較簡單,大部分我都是直接複製以前的實現改改就行了,還是列一些說明.

PerlinNoise 柏林燥聲生成一張靜態圖

柏林燥聲的原理網上很多講解的,用於生成平滑的圖案,其實可以稍微改下,如加個如時間戳,就可變成一張平滑的動態圖.

PinchDistortion 收縮,凹面鏡

實現類似BulgeDistortion(魚眼效果),都是針對UV變形,BulgeDistortion在設定圓心處UV縮小,意思在原圓心處相同的地方取更近的畫素,這樣就導致影像看起來向外擴張.

而PinchDistortion在設定圓心處,周邊UV放大,意思在圓心處相同的地方取更遠的UV畫素,就導致影像看起來向裡縮.

PoissonBlend 泊松融合

影像融合之泊松編輯(Poisson Editing)(2)

GPUImage的實現應該是一種簡化實現,後面會移植按上面原理求泊松重建方程的實現.

現暫時按照GPUImage裡來實現,他的實現比較簡單,唯一麻煩的,需要第一張輸入圖與輸出圖Ping-pong來回處理,不同於前面savefamelayer的實現,他需要在一楨中來回迴圈讀寫,剛開始想的是如何把當前索引當做UBO傳入shader,但是在一楨中把一個UBO更新多次,首先不知道能否這樣實現,就算能,這種實現並不好,在這引入Vulkan裡的PushConstant的概念,能完美解決這個問題,首先vkCmdPushConstants也是插入到CommandBuffer中,在提交給GPU的時候,也是確定的數值,這樣每楨多次迴圈就可以用PushConstant來表明對應次數.感覺在渲染一批次多模型時用來指定索引也不錯啊.

void VkPoissonBlendLayer::onCommand() {
    // ping-pong 單數次
    paramet.iterationNum = paramet.iterationNum / 2 * 2 + 1;
    for (int32_t i = 0; i < paramet.iterationNum; i++) {
        int32_t pong = i % 2;
        inTexs[0]->addBarrier(cmd, VK_IMAGE_LAYOUT_GENERAL,
                              VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
                              VK_ACCESS_SHADER_READ_BIT);
        inTexs[1]->addBarrier(
            cmd, VK_IMAGE_LAYOUT_GENERAL, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
            pong == 0 ? VK_ACCESS_SHADER_READ_BIT : VK_ACCESS_SHADER_WRITE_BIT);
        outTexs[0]->addBarrier(
            cmd, VK_IMAGE_LAYOUT_GENERAL, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
            pong == 0 ? VK_ACCESS_SHADER_WRITE_BIT : VK_ACCESS_SHADER_READ_BIT);
        vkCmdPushConstants(cmd, layout->pipelineLayout,
                           VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(int32_t),
                           &pong);
        vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE,
                          computerPipeline);
        vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE,
                                layout->pipelineLayout, 0, 1,
                                layout->descSets[0].data(), 0, 0);
        vkCmdDispatch(cmd, sizeX, sizeY, 1);
    }
}
#version 450

layout (local_size_x = 16, local_size_y = 16) in;// gl_WorkGroupSize
layout (binding = 0, rgba8) uniform image2D inTex;
layout (binding = 1, rgba8) uniform readonly image2D inTex1;
layout (binding = 2, rgba8) uniform image2D outTex;

layout (std140, binding = 3) uniform UBO {
    float percent;
} ubo;

layout(push_constant) uniform pushBlock {
    int pong;
} constBlock;

const ivec2 centerT[4] = {
    ivec2(1,0),
    ivec2(-1,0),
    ivec2(0,1),
    ivec2(0,-1)
};

void main(){
    ivec2 uv = ivec2(gl_GlobalInvocationID.xy);
    ivec2 size = imageSize(inTex);
    if(uv.x >= size.x || uv.y >= size.y){
        return;
    }     
    vec4 center = vec4(0);
    if(constBlock.pong == 0){
        center = imageLoad(inTex,uv);
    }else{
        center = imageLoad(outTex,uv);
    }
    vec4 center1 = imageLoad(inTex1,uv);  
    vec4 sum = vec4(0);
    vec4 sum1 = vec4(0);
    for(int i = 0; i < 4; i++){
        ivec2 cuv = uv + centerT[i];
        cuv = max(ivec2(0),min(cuv,size));
        if(constBlock.pong == 0){
            sum += imageLoad(inTex,cuv);
        }else{
            sum += imageLoad(outTex,cuv);
        }
        sum1 += imageLoad(inTex1,cuv);
    }
    vec4 mean = sum / 4.0;
    vec4 diff1 = center1 - sum1 /4.0;
    vec4 grad = mean + diff1;
    
    vec4 result = vec4(mix(center.rgb,grad.rgb,center1.a * ubo.percent),center.a);    
    if(constBlock.pong == 0){
        imageStore(outTex,uv,result);  
    }else{
        imageStore(inTex,uv,result);  
    }
}

在N卡2070上,一次迭代在0.2-0.3ms之間.

這個後面在內部又加了一個節點,用來儲存第一個輸入,應該這個層會改變第一個輸入的內容,而別的層可能還需要這個輸入,所以新增一層儲存第一層輸入.

PrewittEdgeDetection

和SobelEdgeDetection沒多大區別,運算元不同,其實運算元區別也不大,垂直與水平的正對方向上一個是1,一個是2,別的都沒啥區別.

RGBDilation/RGBErosion

把原來的Dilation/Erosion的glsl檔案新增下編譯符,針對處理下,取第一個橫向的程式碼貼出來.

#version 450

layout (local_size_x = 16, local_size_y = 16) in;// gl_WorkGroupSize

#if CHANNEL_RGBA
layout (binding = 0, rgba8) uniform readonly image2D inTex;
layout (binding = 1, rgba8) uniform image2D outTex;
#elif CHANNEL_R8
layout (binding = 0, r8) uniform readonly image2D inTex;
layout (binding = 1, r8) uniform image2D outTex;
#endif

layout (binding = 2) uniform UBO {
	int ksize;	    
} ubo;

#if EROSION 
    #define OPERATE min
    #if CHANNEL_RGBA
        #define INIT_VUL vec4(1.0)
    #elif CHANNEL_R8
        #define INIT_VUL 1.0f
    #endif
#endif

#if DILATION 
    #define OPERATE max
    #if CHANNEL_RGBA
        #define INIT_VUL vec4(0.0)
    #elif CHANNEL_R8
        #define INIT_VUL 0.0f
    #endif
#endif

#if IS_SHARED
// 限定最大核為32

#if CHANNEL_RGBA
    shared vec4 row_shared[16][16*3];
#elif CHANNEL_R8
    shared float row_shared[16][16*3];
#endif

void main(){
    ivec2 uv = ivec2(gl_GlobalInvocationID.xy);
    ivec2 size = imageSize(inTex);
    if(uv.x >= size.x || uv.y >= size.y){
        return;
    } 
    ivec2 locId = ivec2(gl_LocalInvocationID.xy);
    for(int i = 0; i < 3; i++){
        uint gIdx = max(0,min(uv.x+(i-1)*16,size.x-1));
        #if CHANNEL_RGBA
            row_shared[locId.y][locId.x + i*16] = imageLoad(inTex,ivec2(gIdx,uv.y));
        #elif CHANNEL_R8
            row_shared[locId.y][locId.x + i*16] = imageLoad(inTex,ivec2(gIdx,uv.y)).r;
        #endif   
    }
    memoryBarrierShared();
	barrier();
    #if CHANNEL_RGBA
        vec4 result = INIT_VUL;
    #elif CHANNEL_R8
        float result = INIT_VUL;
    #endif
    for(int i =0; i < ubo.ksize; i++){
        int ix = locId.x - ubo.ksize/2 + i;
        #if CHANNEL_RGBA
            vec4 fr = row_shared[locId.y][16 + ix];
            result = OPERATE(fr,result);
        #elif CHANNEL_R8
            float fr = row_shared[locId.y][16 + ix];
            result = OPERATE(fr,result);
        #endif
    }
    imageStore(outTex, uv, vec4(result)); 
}

#else

void main(){
    ivec2 uv = ivec2(gl_GlobalInvocationID.xy);
    ivec2 size = imageSize(inTex);
    if(uv.x >= size.x || uv.y >= size.y){
        return;
    } 
    #if CHANNEL_RGBA
        vec4 result = INIT_VUL;
    #elif CHANNEL_R8
        float result = INIT_VUL;
    #endif
    for(int i = 0; i< ubo.ksize; ++i){
        int x = uv.x-ubo.ksize/2+i;
        x = max(0,min(x,size.x-1));
        #if CHANNEL_RGBA
            vec4 r = imageLoad(inTex,ivec2(x,uv.y));
            result = OPERATE(result,r);
        #elif CHANNEL_R8
            float r = imageLoad(inTex,ivec2(x,uv.y)).r;
            result = OPERATE(result,r);
        #endif
    }
    imageStore(outTex, uv, vec4(result));     
}

#endif

下面對應的Closing/Opening都不用改,加個參數列示是R8/RGBA8就行.

ShiTomasiFeatureDetection

和Noble角點檢測一樣,都和修改HarrisCornerDetection中的角點計算方式,別的流程一樣.

SingleComponentGaussianBlur 單通道高斯模糊

在最開始設計GaussianBlur時,就支援R8/RGBA8/RGBA32F這幾種,要新增也只需要修改glsl的編譯符,新增對應邏輯就行.

SobelEdgeDetection/Sketch Sobel邊緣檢波/草圖素描效果

SobelEdgeDetection利用Sobel運算元計算邊緣,而Sketch就是SobelEdgeDetection結果的反轉.

下面的ThresholdEdgeDetection/ThresholdSketch在這二個基礎上加了個Threshold用來確定結果是0還是1.

SmoothToon 高斯模糊後的卡通效果

Toon是卡通效果,而SmoothToon就是先對輸入影像高斯模糊後,然後再應用卡通效果.

VoronoiConsumer 接收Voronoi對映,並使用該對映過濾傳入的影像

二個輸入,第二個輸出需要長寬同為2^n的相同整數,根據第一張圖中的UV,對應第二張圖中的值,生成一個UV,然後取第一張圖值,有點類似lookup對映.

ZoomBlur 將定向運動模糊應用於影像

實現大致同MotionBlur,取周邊點的演算法稍微不同.

中間沒有移植的濾鏡統計

  1. GPUImageParallelCoordinateLineTransform,同上篇文章裡的LineGenerator,還沒找到合適的使用GPGPU高效畫線方法.

  2. GPUImageSolidColorGenerator,在vulkan裡,完全可以用vkCmdClearColorImage替換,效率更高,封裝實現方法就在基類VkLayer::clearColor裡.

  3. GPUImageToneCurveFilter暫時不移植.看實現有點類似Lookup,但是需要根據傳入的資料生成一張查詢表.

  4. GPUImageTransformFilter GPUImage利用頂點著色器,可以進行更多視角轉換,現aoce_vulkan模組裡有上下,左右,以及90/270轉換,這個後面再仔細考慮下如何完善.

歸類

GPUImage里根據裡分四類,在API匯出標頭檔案VkExtraExport.hpp里根據這四類重新排下,加下注釋,如果更新引數是結構,相應結構新增下對應每個引數註釋.

在其layer資料夾,對多個類進行合併,如幾乎所有混合模式的類,實現都在glsl上,幾乎不需要針對基類VkLayer做任何修改,所以都合併在VkBlendingModeLayer檔案裡,色彩調整/視覺效果也合併一些普通的類在對應的VkColorAdjustmentlayer/VkVisualEffectLayer中,而影像處理的類相對來說會複雜點,大部分都是分散到各個集合中,如其中的Dilation/Erosion/Closing/Opening合併到VkMorphLayer檔案中,邊緣檢測的幾個類實現在VkEdgeDetectionLayer中.

相關文章