<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          高斯模糊的 GLSL 實(shí)現(xiàn)

          共 4489字,需瀏覽 9分鐘

           ·

          2021-07-07 23:18

          本文是 OpenGL 4.0 Shading Language Cookbook 的學(xué)習(xí)筆記。


          主要介紹使用GLSL來實(shí)現(xiàn)高斯模糊。


          模糊過濾可以有效減少圖像中的噪點(diǎn)數(shù)量。前面提到,在邊緣檢測之前使用模糊過濾可以減少圖像的高頻變化。


          模糊過濾的基本原理是對附近像素進(jìn)行加權(quán)和來混合當(dāng)前像素顏色。通常使用的權(quán)重隨距離減小(二維屏幕空間距離),距離當(dāng)前像素較遠(yuǎn)的像素貢獻(xiàn)較小。


          高斯模糊使用二維高斯函數(shù)來計(jì)算附近像素的權(quán)重:

          sigma 的平方項(xiàng)是高斯函數(shù)的方差,它決定了高斯曲面的寬度。高斯函數(shù)在(0,0)處取得最大值,對應(yīng)需要模糊處理的像素,隨著x或y坐標(biāo)的增加,高斯函數(shù)值減小。


          下圖顯示了  平方為4.0時(shí)的二維高斯函數(shù)的變化趨勢。

          下圖顯示了高斯模糊前后的畫面,左邊還沒有進(jìn)行高斯模糊,右邊是進(jìn)行高斯模糊后的圖像:

          使用高斯模糊,對于每一個(gè)像素,我們需要計(jì)算所有像素的加權(quán)和。這一算法存在下面這些問題:


          • 算法復(fù)雜度是大 On 的平方,對于實(shí)時(shí)渲染來說太慢了。

          • 為了避免改變圖像整體亮度,所有權(quán)重相加的和必須為1。


          由于我們是在離散的坐標(biāo)上使用高斯函數(shù)采樣,我們的權(quán)重和也不等于1。


          我們可以通過限制采樣的像素?cái)?shù)目,然后對高斯函數(shù)的值進(jìn)行規(guī)范化來解決上面的兩個(gè)問題。在這里,我們使用9x9大小的高斯模糊過濾器,在這種情況下,對于每個(gè)像素,我們只需要采樣81個(gè)像素就可以完成模糊操作。


          這個(gè)方法需要為每個(gè)像素在片段著色器中訪問紋理元素81次。一張800x600大小的圖像,需要訪問紋理元素800x600x81=38,880,000次。看起來訪問次數(shù)非常多?我們可以通過進(jìn)行兩遍高斯模糊來減小訪問次數(shù)。


          上面公式表明,我們可以使用兩遍處理來實(shí)現(xiàn)高斯模糊。在第一遍處理時(shí),我們計(jì)算上面公式中與j相關(guān)的和,將結(jié)果存儲(chǔ)在臨時(shí)的紋理中。在第二遍處理時(shí),我們使用臨時(shí)紋理計(jì)算與i相關(guān)的和。


          現(xiàn)在,還有一個(gè)重要的問題要處理。之前說過,我們的權(quán)值和必須等于1,所以,我們改寫我們的等式為下面的形式來規(guī)范化權(quán)值:

          讓我們把重點(diǎn)轉(zhuǎn)移到代碼上。


          我們在這里通過三遍處理和兩張紋理實(shí)現(xiàn)高斯模糊。第一遍處理時(shí),我們渲染整個(gè)場景到紋理中。在第二遍處理時(shí),我們使用上一遍處理得到的紋理計(jì)算垂直和。最后一遍處理,我們進(jìn)行水平和的計(jì)算,然后將結(jié)果輸出到默認(rèn)的幀緩沖中。


          創(chuàng)建兩個(gè)幀緩沖對象,以及它們綁定的紋理。由于我們在第一遍時(shí)需要繪制三維場景,第一個(gè)幀緩沖對象還需要綁定一個(gè)深度緩沖。第二遍和第三遍處理,我們只需要在片段著色器中繪制二維屏幕,不需要深度緩沖。


          我們將使用子程序來綁定每一遍處理使用的著色器功能。我們的OpenGL程序需要對下面的Uniform變量進(jìn)行設(shè)置:


          • Width:以像素為單位的屏幕寬度。

          • Height:以像素為單位的屏幕高度。

          • Weight[]:規(guī)范化后的高斯權(quán)重?cái)?shù)組。

          • Texture0:設(shè)置為對應(yīng)0號(hào)紋理單元。

          • PixOffset[]:需要模糊處理的像素的位置偏移。


          我們采用下面的步驟實(shí)現(xiàn)高斯模糊:

          1. 使用我們之前介紹的邊緣檢測技術(shù)使用的頂點(diǎn)著色器。

          2. 使用下面的代碼作為片段著色器:


          #version 400
          in vec3 Position; // Vertex position
          in vec3 Normal; // Vertex normal
          in vec2 TexCoord; // Texture coordinate
          uniform sampler2D Texture0;
          uniform int Width; // Width of the screen in pixels
          uniform int Height; // Height of the screen in pixels
          subroutine vec4 RenderPassType();
          subroutine uniform RenderPassType RenderPass;
          // Other uniform variables for
          // the Phong reflection model can
          // be placed here
          layout( location = 0 ) out vec4 FragColor;
          uniform float PixOffset[5]=float[](0.0,1.0,2.0,3.0,
          4.0);
          uniform float Weight[5];
          vec3 phongModel( vec3 pos, vec3 norm )
          {
          // The code for the Phong reflection model
          // goes here
          }
          subroutine (RenderPassType)
          vec4 pass1()
          {
          return vec4(phongModel( Position,Normal ),1.0);
          }
          subroutine( RenderPassType )
          vec4 pass2()
          {
          float dy = 1.0 / float(Height);
          vec4 sum = texture(Texture0, TexCoord) *
          Weight[0];
          for( int i = 1; i < 5; i++ )
          {
          sum += texture( Texture0,
          TexCoord +vec2(0.0,
          PixOffset[i]) * dy )
          * Weight[i];
          sum += texture( Texture0,
          TexCoord - vec2(0.0,
          PixOffset[i]) * dy )
          * Weight[i];
          }
          return sum;
          }

          subroutine( RenderPassType )
          vec4 pass3()
          {
          float dx = 1.0 / float(Width);
          vec4 sum = texture(Texture0, TexCoord) *
          Weight[0];
          for( int i = 1; i < 5; i++ )
          {
          sum += texture( Texture0,
          TexCoord + vec2(PixOffset[i],
          0.0) * dx )
          * Weight[i];
          sum += texture( Texture0,
          TexCoord - vec2(PixOffset[i],
          0.0) * dx )
          * Weight[i];
          }
          return sum;
          }
          void main()
          {
          // This will call either pass1(),
          // pass2(), or pass3()
          FragColor = RenderPass();
          }


          3. 在OpenGL程序中,我們使用PixOffset進(jìn)行坐標(biāo)偏移計(jì)算高斯權(quán)重,然后將結(jié)果存儲(chǔ)在Weight數(shù)組中。代碼如下:


          char uniName[20];
          float weights[5], sum, sigma2 = 4.0f;
          // Compute and sum the weights
          weights[0] = gauss(0,sigma2); // The 1-D Gaussian
          // function
          sum = weights[0];
          for( int i = 1; i < 5; i++ ) {
          weights[i] = gauss(i, sigma2);
          sum += 2 * weights[i];
          }
          // Normalize the weights and set the uniform
          for( int i = 0; i < 5; i++ ) {
          snprintf(uniName, 20, "Weight[%d]", i);
          prog.setUniform(uniName, weights[i] / sum);
          }


          對于第一遍處理,我們采取以下步驟:

          1. 選擇進(jìn)行渲染的幀緩沖,啟用深度測試,清除顏色/深度緩沖區(qū)。

          2. 選擇執(zhí)行pass1子程序。

          3. 繪制場景。


          對于第二遍處理,我們采取以下步驟:

          1. 選擇中轉(zhuǎn)使用的幀緩沖,關(guān)閉深度測試,清除顏色緩沖區(qū)。

          2. 選擇執(zhí)行pass2子程序。

          3. 設(shè)置視圖,投影和模型矩陣為單位矩陣。

          4. 綁定第一遍處理生成的紋理到0號(hào)紋理單元。

          5. 繪制中間紋理。


          對于第三遍處理,我們采取以下步驟:

          1. 解除綁定中轉(zhuǎn)幀緩沖(設(shè)置為默認(rèn)的幀緩沖),清除顏色緩沖區(qū)。

          2. 選擇執(zhí)行pass3子程序。

          3. 綁定第二遍處理生成的紋理到0號(hào)紋理單元。

          4. 繪制紋理到屏幕。

          原理

          步驟3中使用的gauss函數(shù)的第一個(gè)參數(shù)是x坐標(biāo),第二個(gè)參數(shù)是 sigma 的平方。gauss函數(shù)關(guān)于坐標(biāo)0進(jìn)行偏移,我們只需要使用偏移量作參數(shù)即可。在這里,我們只使用正整數(shù)來進(jìn)行偏移,我們需要對非0數(shù)值的結(jié)果乘以2(對應(yīng)整數(shù)和負(fù)數(shù)偏移)。


          第一遍處理(pass1子程序),我們使用Phong反射模型將場景渲染到紋理中。


          第二遍處理進(jìn)行垂直權(quán)重和高斯模糊操作,并將結(jié)果存儲(chǔ)在另一張紋理中。我們使用PixOffset數(shù)組來進(jìn)行垂直方向的坐標(biāo)偏移求出權(quán)重和。(dy項(xiàng)是一個(gè)紋理元素在紋理坐標(biāo)下的高度)我們同時(shí)累加兩個(gè)方向(距離要模糊的像素垂直方向正反兩邊四個(gè)像素內(nèi)的像素)的權(quán)重。


          第三遍處理和第二遍處理類似。我們計(jì)算水平方向的權(quán)重和。這一遍處理后,我們將結(jié)果直接輸出到默認(rèn)的幀緩沖。

          優(yōu)化

          我們還可以繼續(xù)優(yōu)化將訪問紋理的次數(shù)減少一半。

          如果我們好好利用紋理訪問時(shí)自動(dòng)進(jìn)行的線性插值(紋理的縮放模式為GL_LINEAR),我們就可以一次獲得兩個(gè)紋理元素的信息!Daniel Rákos的博客詳細(xì)描述了這一技術(shù)(rastergrid.com/blog/201)。


          我們可以通過增加Weight和PixOffset數(shù)組的大小來增大模糊效果。還可以使用不同的sigma的平方值來改變高斯函數(shù)的趨勢。


          來源:https://zhuanlan.zhihu.com/p/60824750

          作者:fangcun


          推薦:

          Android FFmpeg 實(shí)現(xiàn)帶濾鏡的微信小視頻錄制功能

          全網(wǎng)最全的 Android 音視頻和 OpenGL ES 干貨,都在這了

          一文掌握 YUV 圖像的基本處理

          Android FFmpeg 流媒體邊播放邊錄制功能

          FFmpeg + Android AudioRecorder 音頻錄制編碼

          利用 FFmpeg 和 ANativeWindow 實(shí)現(xiàn)視頻解碼播放

          FFmpeg、x264以及fdk-aac 編譯整合

          瀏覽 128
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  91精品国产综合久久久不卡电影 | 三级在线网 | 青娱网电信一区电信二区电信三区 | 欧美黄色成人视频 | 久青久久|