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

          WebGL 處理圖片,來玩

          共 6289字,需瀏覽 13分鐘

           ·

          2021-07-30 00:59

          “咕咕咕咕咕...”

          天空中傳來了鴿群的聲音,隨之而來的是悅耳的鴿哨兒??

          近日在 凹凸實驗室 公眾號看到一篇文章《GLSL 著色器,來玩》,你們都來玩,那我走?

          玩笑話,其內(nèi)容還是有趣的,對于我這種 WebGL 青瓜蛋子還是十分友好;環(huán)境搭建直接使用了 ThreeJS,這樣就可以讓學(xué)習(xí)繁雜的 WebGL 基礎(chǔ) API 變成黑盒,讓新手更易學(xué)習(xí)。當(dāng)然如果想用原生 WebGL 標準 API 來繪制,可以看我公眾號的 WebGL 專欄。

          既然那么好玩,索性跟風(fēng)也寫個著色器的小文章來玩吧??

          小序

          何為著色器,簡而言之即為自定義 GPU 處理圖形能力的程序,GLSL 基礎(chǔ)語法不在本文涉及范圍內(nèi),讀者可自行閱讀《GLSL ES 語法基礎(chǔ)》。

          既然要玩,也要玩點東西出來,即要有點小成果;凹凸實驗室 文章中以動態(tài)渲染不同顏色為例,那本文就以渲染圖片為例,做一個可以切換圖片渲染效果的 Demo 吧(可以理解為切換濾鏡效果)。

          Demo 所涉及 GLSL 基礎(chǔ)知識并未超出《GLSL ES 語法基礎(chǔ)》一文之所及,僅應(yīng)用了些許數(shù)學(xué)方面的小知識,從而實現(xiàn)圖片不同的渲染效果。

          環(huán)境搭建

          Demo 中并未使用任何第三方 WebGL 庫,只使用了自己用 TypeScript 重寫的 webgl-utils,整個項目框架使用了 webpack-react-template(兩個項目 GitHub 鏈接會附在文末)。要處理 GLSL 文件,故需對 webpack.config.js 進行些許修改:

          // webpack.config.js

          const config = {
            // ...
            module: {
              rules: [
                // ...
                {
                  test/\.(svg|glsl)$/,
                  issuer/\.(js|ts)x?$/,
                  use: [
                    {
                      loader"raw-loader",
                    },
                  ],
                },
              ],
            },
          };

          僅需讓 raw-loader 處理一下 GLSL 文件,順便在 types.d.ts 中加入對 GLSL 文件的聲明即可:

          declare module '*.glsl' {
            export default '' as string;
          }

          程序主體

          整個程序主體由下面幾行代碼組成:

          const Main = ({ type }: Props) => {
          const canvas = useRef<HTMLCanvasElement>({} as HTMLCanvasElement);
          const [gl, setGL] = useState<WebGL2RenderingContext | null>(null);
          const [program, setProgram] = useState<WebGLProgram | null>(null);

          useEffect(() => {
          canvas.current.width = Math.floor(window.innerWidth * 0.6);
          canvas.current.height = Math.floor(window.innerHeight * 0.7);
          const ctx = canvas.current.getContext('webgl2');

          if (!ctx) {
          throw new Error('Failed to get WebGL2 context');
          }

          setGL(ctx);
          const p = createProgramFromSources(ctx, ShadersMap[type], [], []);
          setProgram(p);
          }, [type]);

          const animation = useCallback(() => {
          if (gl && program) {
          void render(gl, program, Image);
          }
          }, [gl, program]);

          requestAnimationFrame(() => {
          animation();
          });

          return (
          <canvas ref={canvas} id="canvas" />
          );
          };

          而最重要的 render 職責(zé)就是加載并渲染圖片,WebGL 如何加載圖片,大家可以閱讀 《WebGL 紋理映射》。紋理映射中選用了 超級賽亞人之神 圖片,這次就選 我妻善逸 ,渲染后效果如下圖:

          本次主要做了:交換紅藍通道、灰白、高斯模糊以及馬賽克四種效果,其中除了簡單的數(shù)學(xué)知識外,還涉及到了一個重要的知識點 切換著色器。以下例子中頂點著色器都無需修改,只需對片元著色器進行修改即可。

          原圖

          渲染原圖的頂點著色器和片元著色器很簡單:

          // vertex-shader.glsl
          #version 300 es

          in vec2 a_Position;
          in vec2 a_TexCoord;
          uniform vec2 u_Resolution;
          out vec2 v_TexCoord;

          void main() {
          vec2 zeroToOne = a_Position / u_Resolution;
          vec2 zeroToTwo = zeroToOne * 2.0;
          vec2 clipSpace = zeroToTwo - 1.0;

          gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
          v_TexCoord = a_TexCoord;
          }

          上面有個小點需要注意,頂點著色器程序中將絕對的像素位置轉(zhuǎn)換到了 區(qū)間內(nèi),至于為何乘的為 而非 則取決于 gl.texParameteri 的設(shè)置。

          // fragment-shader.glsl
          #version 300 es

          precision highp float;

          uniform sampler2D u_Image;
          in vec2 v_TexCoord;
          out vec4 outColor;

          void main() {
          outColor = texture(u_Image, v_TexCoord);
          }

          切換紅藍通道

          片元著色器中的顏色是 rgba 形式的 vec4 變量,交換紅藍通道只需按如下變量賦值即可:

          // ...

          void main() {
          outColor = texture(u_Image, v_TexCoord).bgra;
          }

          渲染效果如下:

          著實有些瘆人 :(

          灰白圖

          將彩色圖片轉(zhuǎn)成灰白圖片,只需將 rgb 三個通道的顏色值進行平均:

          // ...

          void main() {
          vec4 color = texture(u_Image, v_TexCoord);
          float average = (color.r + color.g + color.b) / 3.0;
          outColor = vec4(average, average, average, color.a);
          }

          高斯模糊

          高斯模糊(Gaussian Blur)是一種很常見的效果,通常用來降低噪聲或降低細節(jié)層次,我們可以在 PhotoShop 的專業(yè)繪圖軟件中見到其身影。

          它使用正態(tài)分布計算圖像中每個像素的變換,分布不為 0 的像素組成的卷積矩陣與原圖像做變換,每個像素值都是周圍元素的加權(quán)平均。因為圖片是二維信息,所以要使用二維正態(tài)分布,如下圖:

          (圖片來源:https://images0.cnblogs.com/blog/502930/201309/11201048-3f10d0cc1d9d4d65a7326e467fc5bc11.jpg)

          原像素有最大的二維正態(tài)分布值,即有最大權(quán)重,故模糊后的像素最接近原像素,模糊后的整個圖像還能看出原圖像的影子。簡單來說,高斯模糊的過程就是原圖像與二維正態(tài)分布做卷積,不再展開講(因為我也不會)??

          #version 300 es

          precision highp float;

          uniform sampler2D u_Image;
          uniform vec2 u_Resolution;
          in vec2 v_TexCoord;
          out vec4 outColor;

          // 每個像素的權(quán)重
          // 最中間的為原像素,權(quán)重最高
          float weight[9] = float[] (
          0.0947416, 0.118318, 0.0947416,
          0.118318, 0.147761, 0.118318,
          0.0947416, 0.118318, 0.0947416
          );

          void main() {
          vec4 color;

          for(int i = 0; i < 9; i++) {
          vec2 coord;
          coord.x = v_TexCoord.x + float(i % 3 - 1) / u_Resolution.x;
          coord.y = v_TexCoord.y + float(int(i / 3) - 1) / u_Resolution.y;
          color = color + texture(u_Image, coord) * weight[i];
          }

          outColor = color;
          }

          對原像素方圓 1 像素的值做加權(quán)平均,效果如下圖:

          仔細觀察還是可以發(fā)現(xiàn)差別的:

          馬賽克

          最后來講一個令人“厭惡”的效果 —— 馬賽克,好像任何東西打了碼之后就會變得很邪惡??

          實現(xiàn)馬賽克這個效果需引入另一個概念 —— 噪聲,很容易理解就是多余不必要的干擾信息。實現(xiàn)也很簡單,我們只需生成一個“第三者”來“插足”原圖即可:

          #version 300 es

          precision highp float;

          uniform sampler2D u_Image;
          uniform vec2 u_Resolution;
          in vec2 v_TexCoord;
          out vec4 outColor;

          float random(vec2 st) {
          return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453);
          }

          void main() {
          vec2 st = v_TexCoord.xy / u_Resolution.xy * 20000.0;
          vec2 ipos = floor(st);
          vec3 color = vec3(random(ipos));
          vec4 tex = texture(u_Image, v_TexCoord);

          outColor = vec4(tex.rgb * 0.2 + color * 0.8, 1.0);
          }

          此處的“第三者”就是根據(jù)紋理像素坐標生成的 color,然后根據(jù)相應(yīng)權(quán)重與原圖疊加在一起:

          其實真正的“打碼”并不是這種方式,打碼一般是將指定區(qū)域內(nèi)的內(nèi)容做加權(quán)平均,然后讓該區(qū)域內(nèi)的像素展示相同的顏色。比如我們以微信截圖的馬賽克為例:

          我們使用截圖工具對 cdd(臭弟弟) 打碼后效果:

          會發(fā)現(xiàn)截圖工具每次將“步兵”轉(zhuǎn)成“騎兵”后,每個馬賽克像素大小的大小以及位置都是相同的,并且針對于同一個圖片打碼后的效果也都是相同的。這就是對圖片的特定大小區(qū)域內(nèi)的內(nèi)容做加權(quán)平均并設(shè)為相同顏色后的效果。這種效果各位可以自己嘗試實現(xiàn)。

          切換著色器??

          上面我們編寫了五種不同的片元著色器,但如何在一個程序中使用這五種不同的著色器呢?

          回想一下在哪兒我們用到了 Shader?是不是在 gl.attachShader 的時候?所以當(dāng)我們想使用不同的 Shader 時,我們直接使用新的 programattachShader 即可(記得要 useProgram),效果如下:

          篇末不點題

          WebGL 基礎(chǔ) API 知識量為 1,則其所涉及的其他領(lǐng)域知識(諸如圖形學(xué)、數(shù)學(xué)、物理等)可能為 100 甚至更多,唯有興趣趨勢才有激情和動力前進。

          最近項目較忙,故停更一陣,畢竟生活、工作才是重心;后續(xù)會繼如以往,分享感興趣、有趣、有用的內(nèi)容,但至于面試等相關(guān)文章,俯拾即是,不寫也罷。


          課外輔導(dǎo)鏈接??

          知識點學(xué)習(xí)??

          1. 高斯模糊:https://zh.wikipedia.org/wiki/%E9%AB%98%E6%96%AF%E6%A8%A1%E7%B3%8A
          2. 正態(tài)分布:https://zh.wikipedia.org/wiki/%E6%AD%A3%E6%80%81%E5%88%86%E5%B8%83
          3. 卷積:https://zh.wikipedia.org/wiki/%E5%8D%B7%E7%A7%AF

          GitHub Repo

          1. webgl-utils-ts:https://github.com/LiJiahaoCoder/webgl-utils-ts
          2. webpack-react-template:https://github.com/LiJiahaoCoder/webpack-react-template
          3. 本文示例源碼:https://github.com/LiJiahaoCoder/webgl-process-image
          瀏覽 118
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  青青激情视频免费观看在线观看 | 无码视频网 | 丰满老妇高潮一级A片 | 大屌操逼视频 | 青娱乐91精品 |