<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

          共 12356字,需瀏覽 25分鐘

           ·

          2021-09-12 00:41

          關(guān)注并將「趣談前端」設(shè)為星標(biāo)

          每天定時分享技術(shù)干貨/優(yōu)秀開源/技術(shù)思維

          之前群里有粉絲提問, 就是shader不是很理解。然后今天他就來了, 廢話不多說,讀完今天的這篇文章你可以學(xué)到以下幾點:

          1. 為什么需要有shader ? shader的作用是什么????
          2. shader 中的每個參數(shù)到底是什么意思??怎么去用???

          你如果會了,這篇文章你可以不用看??,不用浪費時間,去看別的文章。如果哪里寫的有問題歡迎大家指正,我也在不斷地學(xué)習(xí)當(dāng)中。

          why need shader

          這里我結(jié)合自己的思考??,講講webgl的整個的一個渲染過程。

          渲染管線

          「Webgl」的渲染依賴底層「GPU」的渲染能力。所以「WEBGL」 渲染流程和 「GPU」 內(nèi)部的渲染管線是相符的。

          「渲染管線的作用是將3D模型轉(zhuǎn)換為2維圖像。」

          在早期,渲染管線是不可編程的,叫做「固定渲染管線」,工作的細(xì)節(jié)流程已經(jīng)固定,修改的話需要調(diào)整一些參數(shù)。

          現(xiàn)代的 「GPU」 所包含的渲染管線為「可編程渲染管線」,可以通過編程 「GLSL 著色器語言」 來控制一些渲染階段的細(xì)節(jié)。

          簡單來說:就是使用「shader」,我們可以對畫布中「每個像素點做處理」,然后就可以生成各種酷炫的效果了。

          渲染過程

          渲染過程大概經(jīng)歷了下面這么多過程, 因為本篇文章的重點其實是在著色器,所以我重點分析從「頂點著色器」—— 「片元著色器」的一個過程

          • 「頂點著色器」
          • 「圖片裝配」
          • 「光柵化」
          • 「片元著色器」
          • 「逐片段操作(本文不會分享此內(nèi)容)」
          • 「裁剪測試」
          • 「多重采樣操作」
          • 「背面剔除」
          • 「模板測試」
          • 「深度測試」
          • 「融合」
          • 「緩存」

          頂點著色器

          WebGL就是和GPU打交道,在GPU上運行的代碼是一對著色器,一個是頂點著色器,另一個是片元著色器。每次調(diào)用著色程序都會先執(zhí)行頂點著色器,再執(zhí)行片元著色器。

          一個頂點著色器的工作是生成裁剪空間坐標(biāo)值,通常是以下的形式:

          const vertexShaderSource = `
              attribute vec3 position; 
              void main() {
                  gl_Position = vec4(position,1); 
              }
          `

          每個頂點調(diào)用一次(頂點)著色器,每次調(diào)用都需要設(shè)置一個特殊的全局變量 「gl_Position」。該變量的值就是裁減空間坐標(biāo)值。這里有同學(xué)就問了, 什么是「裁剪空間的坐標(biāo)值」???

          其實我之前有講過,我在講一遍。

          何為裁剪空間坐標(biāo)?就是無論你的畫布有多大,裁剪坐標(biāo)的坐標(biāo)范圍永遠(yuǎn)是 -1 到 1 。

          看下面這張圖:

          裁剪坐標(biāo)系

          如果運行一次頂點著色器, 那么gl_Position  就是**(-0.5,-0.5,0,1)** 記住他永遠(yuǎn)是個 「Vec4」,  簡單理解就是對應(yīng)「x、y、z、w」。即使你沒用其他的,也要設(shè)置默認(rèn)值, 這就是所謂的 3維模型轉(zhuǎn)換到我們屏幕中。

          頂點著色器需要的數(shù)據(jù),可以通過以下四種方式獲得。

          1. attributes 屬性(從緩沖讀取數(shù)據(jù))
          2. uniforms 全局變量 (一般用來對物體做整體變化、 旋轉(zhuǎn)、縮放)
          3. textures 紋理(從像素或者紋理獲得數(shù)據(jù))
          4. varyings 變量  (將頂點著色器的變量 傳給 片元著色器)

          Attributes 屬性

          屬性可以用 float, vec2, vec3, vec4, mat2, mat3mat4 數(shù)據(jù)類型

          所以它內(nèi)建的數(shù)據(jù)類型例如vec2, vec3vec4分別代表兩個值,三個值和四個值, 類似的還有mat2, mat3mat4 分別代表 2x2, 3x3 和 4x4 矩陣。你可以做一些運算例如常量和矢量的乘法。看幾個例子吧:

          vec4 a = vec4(1234);
          vec4 b = a * 2.0;
          // b 現(xiàn)在是 vec4(2, 4, 6, 8);

          向量乘法 和矩陣乘法 :

          mat4 a = ???
          mat4 b = ???
          mat4 c = a * b;
           
          vec4 v = ???
          vec4 y = c * v;

          它還支持矢量「調(diào)制」,意味者你可以交換或重復(fù)分量。

          v.yyyy  ===  vec4(y, y, y,y )
          v.bgra  ===  vec4(v.b,v.g,v.r,v.a)
          vec4(v.rgb, 1) ===  vec4(v.r, v.g, v.b, 1
          vec4(1) === vec4(1111)

          這樣你在處理圖片的時候可以輕松進(jìn)「行 顏色通道 對調(diào)」, 發(fā)現(xiàn)你可以實現(xiàn)各種各樣的濾鏡了。

          后面的屬性在下面實戰(zhàn)中會講解:我們接著往下走:

          圖元裝配和光柵化

          「什么是圖元?」

          ?

          「描述各種圖形元素的函數(shù)叫做圖元,描述幾何元素的稱為幾何圖元(點,線段或多邊形)。點和線是最簡單的幾何圖元」經(jīng)過頂點著色器計算之后的坐標(biāo)會被組裝成「組合圖元」。

          ?

          「通俗解釋」「圖元就是一個點、一條線段、或者是一個多邊形。」

          「什么是圖元裝配呢?」

          「簡單理解就是說將我們設(shè)置的頂點、顏色、紋理等內(nèi)容組裝稱為一個可渲染的多邊形的過程?!?/strong>

          組裝的類型取決于:你最后繪制選擇的圖形類型

          gl.drawArrays(gl.TRIANGLES, 03)

          「如果是三角形的話,頂點著色器就執(zhí)行三次」

          光柵化

          「什么是光柵化:」

          通過圖元裝配生成的多邊形,計算像素并填充,「剔除」不可見的部分,「剪裁」掉不在可視范圍內(nèi)的部分。最終生成可見的帶有顏色數(shù)據(jù)的圖形并繪制。

          「光柵化流程圖解:」

          光珊化圖解

          剔除和剪裁

          • 「剔除」

            在日常生活中,對于不透明物體,背面對于觀察者來說是不可見的。同樣,在「webgl」中,我們也可以設(shè)定物體的背面不可見,那么在渲染過程中,就會將不可見的部分剔除,不參與繪制。節(jié)省渲染開銷。

          • 「剪裁」

            日常生活中不論是在看電視還是觀察物體,都會有一個可視范圍,在可視范圍之外的事物我們是看不到的。類似的,圖形生成后,有的部分可能位于可視范圍之外,這一部分會被剪裁掉,不參與繪制。以此來提高性能。這個就是「視椎體」, 在??范圍內(nèi)能看到的東西,才進(jìn)行繪制。

          片元著色器

          「光珊化后,每一個像素點都包含了 顏色 、深度 、紋理數(shù)據(jù), 這個我們叫做片元」

          ?

          小tips :每個像素的顏色由片元著色器的「gl_FragColor」提供

          ?

          接收光柵化階段生成的片元,在光柵化階段中,已經(jīng)計算出每個片元的顏色信息,這一階段會將片元做逐片元挑選的操作,處理過的片元會繼續(xù)向后面的階段傳遞。「片元著色器運行的次數(shù)由圖形有多少個片元決定的」

          「逐片元挑選」

          通過模板測試和深度測試來確定片元是否要顯示,測試過程中會丟棄掉部分無用的片元內(nèi)容,然后生成可繪制的二維圖像繪制并顯示。

          • **深度測試:**就是對 「z」 軸的值做測試,值比較小的片元內(nèi)容會覆蓋值比較大的。(類似于近處的物體會遮擋遠(yuǎn)處物體)。
          • **模板測試:**模擬觀察者的觀察行為,可以接為鏡像觀察。標(biāo)記所有鏡像中出現(xiàn)的片元,最后只繪制有標(biāo)記的內(nèi)容。

          實戰(zhàn)——繪制個三角形

          在進(jìn)行實戰(zhàn)之前,我們先給你看一張圖,讓你能大概了解,用原生webgl生成一個三角形需要那些步驟:

          draw

          我們就跟著這個流程圖一步一步去操作:

          初始化canvas

          新建一個webgl畫布

          <canvas id="webgl" width="500" height="500"></canvas>

          創(chuàng)建webgl 上下文:

          const gl = document.getElementById('webgl').getContext('webgl')

          創(chuàng)建著色器程序

          著色器的程序這些代碼,其實是重復(fù)的,我們還是先看下圖,看下我們到底需要哪些步驟:

          shader

          那我們就跟著這個流程圖:一步一步來好吧。

          創(chuàng)建著色器

           const vertexShader = gl.createShader(gl.VERTEX_SHADER)
           const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)

          gl.VERTEX_SHADER  和 gl.FRAGMENT_SHADER  這兩個是全局變量 分別表示「頂點著色器」「片元著色器」

          綁定數(shù)據(jù)源

          顧名思義:數(shù)據(jù)源,也就是我們的著色器 代碼。

          編寫著色器代碼有很多種方式:

          1. 用 script 標(biāo)簽  type  notjs 這樣去寫
          2. 模板字符串 (比較喜歡推薦這種)

          我們先寫頂點著色器:

          const vertexShaderSource = `
              attribute vec4 a_position;
              void main() {
                  gl_Position = a_position;
              }
           `

          頂點著色器 必須要有 main 函數(shù) ,他是強類型語言, 「記得加分號哇」 不是js 兄弟們。我這段著色器代碼非常簡單   定義一個vec4 的頂點位置, 然后傳給 gl_Position

          這里有小伙伴會問 ?這里「a_position」一定要這么搞??

          這里其實是這樣的哇, 就是我們一般進(jìn)行變量命名的時候  都會增加帶有關(guān)鍵詞的前綴 用來區(qū)分每個變量的名字 他是屬性 還是 全局變量 還是紋理   比如這樣:

          uniform mat4 u_mat;

          表示個矩陣,如果不這樣也可以哈。但是要專業(yè)唄,防止bug 影響。

          我們接著寫片元著色器:

          const fragmentShaderSource = `
              void main() {
                  gl_FragColor = vec4(1.0,0.0,0.0,1.0);
              }
          `

          這個其實理解起來非常簡單哈, 每個像素點的顏色 是紅色 , gl_FragColor 其實對應(yīng)的是 「rgba」  也就是顏色的表示。

          有了數(shù)據(jù)源之后開始綁定:

          // 創(chuàng)建著色器
          const vertexShader = gl.createShader(gl.VERTEX_SHADER)
          const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
          //綁定數(shù)據(jù)源
          gl.shaderSource(vertexShader, vertexShaderSource)
          gl.shaderSource(fragmentShader, fragmentShaderSource)

          是不是很簡單哈哈哈哈,我覺得你應(yīng)該會了。

          后面著色器的一些操作

          其實后面「編譯著色器」、「綁定著色器」「連接著色器程序」、「使用著色器程序」  都是一個api 搞定的事不多說了 直接看代碼:

          // 編譯著色器
          gl.compileShader(vertexShader)
          gl.compileShader(fragmentShader)
          // 創(chuàng)建著色器程序
          const program = gl.createProgram()
          gl.attachShader(program, vertexShader)
          gl.attachShader(program, fragmentShader)
          // 鏈接 并使用著色器
          gl.linkProgram(program)
          gl.useProgram(program)

          這樣我們就創(chuàng)建好了一個著色器程序了。

          這里又有人問,我怎么知道我創(chuàng)建的著色器是對的還是錯的呢?我就是很粗心的人呢???好的他來了 如何調(diào)試:

          const success = gl.getProgramParameter(program, gl.LINK_STATUS)
          if (success) {
            gl.useProgram(program)
            return program
          }
          console.error(gl.getProgramInfoLog(program), 'test---')
          gl.deleteProgram(program)

          「getProgramParameter」  這個方法用來判斷 我們著色器 「glsl」 語言寫的是不是對的, 然后你可以通過 「getProgramInfoLog」這個方法 類似于打 日志 去發(fā)現(xiàn)?了。

          數(shù)據(jù)存入緩沖區(qū)

          有了著色器,現(xiàn)在我們差的就是數(shù)據(jù)了對吧。

          上文在寫頂點著色器的時候用到了Attributes屬性,說明是「這個變量要從緩沖中讀取數(shù)據(jù)」,下面我們就來把數(shù)據(jù)存入緩沖中。

          首先創(chuàng)建一個頂點緩沖區(qū)對象(Vertex Buffer Object, VBO)

          const buffer = gl.createBuffer()

          gl.createBuffer()函數(shù)創(chuàng)建緩沖區(qū)并返回一個標(biāo)識符,接下來需要為WebGL綁定這個buffer

          gl.bindBuffer(gl.ARRAY_BUFFER, buffer)

          gl.bindBuffer()函數(shù)把標(biāo)識符buffer設(shè)置為「當(dāng)前緩沖區(qū)」,后面的所有的數(shù)據(jù)都會都會被放入當(dāng)前緩沖區(qū),「直到bindBuffer綁定另一個當(dāng)前緩沖區(qū)」

          我們新建一個數(shù)組 然后并把數(shù)據(jù)存入到緩沖區(qū)中。

          const data = new Float32Array([0.00.0-0.3-0.30.3-0.3])
          gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)

          因為「JavaScript與WebGL通信必須是二進(jìn)制的」,不能是傳統(tǒng)的文本格式,所以這里使用了ArrayBuffer對象將數(shù)據(jù)轉(zhuǎn)化為二進(jìn)制,因為頂點數(shù)據(jù)是浮點數(shù),精度不需要太高,所以使用Float32Array就可以了,這是JavaScript與GPU之間大量實時交換數(shù)據(jù)的有效方法。

          「gl.STATIC_DRAW」  指定數(shù)據(jù)存儲區(qū)的使用方法:緩存區(qū)的內(nèi)容可能會經(jīng)常使用,但是不會更改

          「gl.DYNAMIC_DRAW」 表示 緩存區(qū)的內(nèi)容經(jīng)常使用,也會經(jīng)常更改。

          「gl.STREAM_DRAW」 表示緩沖區(qū)的內(nèi)容可能不會經(jīng)常使用

          從緩沖中讀取數(shù)據(jù)

          「GLSL」著色程序的唯一輸入是一個屬性值「a_position」。我們要做的第一件事就是從剛才創(chuàng)建的GLSL著色程序中找到這個屬性值所在的位置。

          const aposlocation = gl.getAttribLocation(program, 'a_position')

          接下來我們需要告訴「WebGL」怎么從我們之前準(zhǔn)備的緩沖中獲取數(shù)據(jù)給著色器中的屬性。首先我們需要啟用對應(yīng)屬性

          gl.enableVertexAttribArray(aposlocation)

          最后是從緩沖中讀取數(shù)據(jù)綁定給被激活的「aposlocation」的位置

          gl.vertexAttribPointer(aposlocation, 2, gl.FLOAT, false00)

          gl.vertexAttribPointer()函數(shù)有六個參數(shù):

          1. 讀取的數(shù)據(jù)要綁定到哪
          2. 表示每次從緩存取幾個數(shù)據(jù),也可以表示每個頂點有幾個單位的數(shù)據(jù),取值范圍是1-4。這里每次取2個數(shù)據(jù),之前vertices聲明的6個數(shù)據(jù),正好是3個頂點的二維坐標(biāo)。
          3. 表示數(shù)據(jù)類型,可選參數(shù)有g(shù)l.BYTE有符號的8位整數(shù),gl.SHORT有符號的16位整數(shù),gl.UNSIGNED_BYTE無符號的8位整數(shù),gl.UNSIGNED_SHORT無符號的16位整數(shù),gl.FLOAT32位IEEE標(biāo)準(zhǔn)的浮點數(shù)。
          4. 表示是否應(yīng)該將整數(shù)數(shù)值歸一化到特定的范圍,對于類型gl.FLOAT此參數(shù)無效。
          5. 表示每次取數(shù)據(jù)與上次隔了多少位,0表示每次取數(shù)據(jù)連續(xù)緊挨上次數(shù)據(jù)的位置,WebGL會自己計算之間的間隔。
          6. 表示首次取數(shù)據(jù)時的偏移量,必須是字節(jié)大小的倍數(shù)。0表示從頭開始取。

          渲染

          現(xiàn)在著色器程序 和數(shù)據(jù)都已經(jīng)ready 了, 現(xiàn)在就差渲染了。渲染之前和2d canvas 一樣做一個清除畫布的動作:

          // 清除canvas
          gl.clearColor(0000)
          gl.clear(gl.COLOR_BUFFER_BIT)

          我們用「0、0、0、0」清空畫布,分別對應(yīng) 「r, g, b, alpha (紅,綠,藍(lán),阿爾法」)值, 所以在這個例子中我們讓畫布變透明了。

          開啟繪制三角形:

          gl.drawArrays(gl.TRIANGLES, 0, 3)
          1. 「第一個參數(shù)表示繪制的類型」
          2. 「第二個參數(shù)表示從第幾個頂點開始繪制」
          3. 「第三個參數(shù)表示繪制多少個點,緩沖中一共6個數(shù)據(jù),每次取2個,共3個點」

          「繪制類型共有下列幾種」 「看圖:」

          drawtype

          這里我們看下畫面是不是一個紅色的三角形 :

          三角形截圖

          我們創(chuàng)建的數(shù)據(jù)是這樣的:

          「畫布的寬度是 500 * 500 轉(zhuǎn)換出來的實際數(shù)據(jù)其實是這樣的」

          0,0  ====>  0,0 
          -0.3-0.3 ====> 175325
          0.3-0.3 ====>  325325

          矩陣的使用

          有了靜態(tài)的圖形我們開始著色器,對三角形做一個縮放。

          改寫頂點著色器:其實在頂點著色器上加一個全局變量  這就用到了 著色器的第二個屬性  uniform

           const vertexShaderSource = `
            attribute vec4 a_position;
            // 添加矩陣代碼
            uniform mat4 u_mat;
            void main() {
                gl_Position = u_mat * a_position;
            }
          `

          然后和屬性一樣,我們需要找到 uniform 對應(yīng)的位置:

          const matlocation = gl.getUniformLocation(program, 'u_mat')

          然后初始化一個縮放舉證:

          // 初始化一個旋轉(zhuǎn)矩陣。
            const mat = new Float32Array([
              Tx,  0.00.00.0,
              0.0,  Ty, 0.00.0,
              0.00.0,  Tz, 0.0,
              0.00.00.01.0,
            ]);

          Tx, Ty, Tz 對應(yīng)的其實就是 x y z 軸縮放的比例。

          最后一步, 將矩陣應(yīng)用到著色器上, 在畫之前, 這樣每個點 就可以?? 這個縮放矩陣了 ,所以整體圖形 也就進(jìn)行了縮放。

          gl.uniformMatrix4fv(matlocation, false, mat)

          三個參數(shù)分別代表什么意思:

          1. 全局變量的位置
          2. 是否為轉(zhuǎn)置矩陣
          3. 矩陣數(shù)據(jù)

          OK 我寫了三角形縮放的動畫:

            let Tx = 0.1 //x坐標(biāo)的位置
            let Ty = 0.1 //y坐標(biāo)的位置
            let Tz = 1.0 //z坐標(biāo)的位置
            let Tw = 1.0 //差值
            let isOver = true
            let step = 0.08
            function run({
              if (Tx >= 3) {
                isOver = false
              }
              if (Tx <= 0) {
                isOver = true
              }
              if (isOver) {
                Tx += step
                Ty += step
              } else {
                Tx -= step
                Ty -= step
              }
              const mat = new Float32Array([
                Tx,  0.00.00.0,
                0.0,  Ty, 0.00.0,
                0.00.0,  Tz, 0.0,
                0.00.00.01.0,
              ]);
              gl.uniformMatrix4fv(matlocation, false, mat)
              gl.drawArrays(gl.TRIANGLES, 03)

              // 使用此方法實現(xiàn)一個動畫
              requestAnimationFrame(run)
            }

          效果圖如下:

          縮放動畫

          最后 給大家看一下webgl 內(nèi)部是怎么搞的 一張gif 動畫 :

          vertex-shader-anim

          原始的數(shù)據(jù)通過 頂點著色器  生成一系列 新的點。

          變量的使用

          說完矩陣了下面??,我們開始說下著色器中的varying 這個變量 是如何和片元著色器進(jìn)行聯(lián)動的。

          我們還是繼續(xù)改造頂點著色器:

          const vertexShaderSource = `
            attribute vec4 a_position;
            uniform mat4 u_mat;
            // 變量
            varying vec4 v_color;
            void main() {
                gl_Position = u_mat * a_position;
                v_color =  gl_Position * 0.5 + 0.5;
            }
          `

          這里有一個小知識 , gl_Position  他的值范圍是  「-1 -1」 但是片元著色 他是顏色 他的范圍是 「0 - 1」 , 所以呢這時候呢,我們就要 做一個范圍轉(zhuǎn)換  所以為什么要 乘 0.5  在加上 0.5 了, 希望你們明白。

          改造下片元著色器:

          const fragmentShaderSource = `
              precision lowp float;
              varying vec4 v_color;
              void main() {
                  gl_FragColor = v_color;
              }
          `

          只要沒一個像素點 改為由頂點著色器傳過來的就好了。

          我們看下這時候的三角形 變成啥樣子了。

          彩色三角形

          是不是變成彩色三角形了, 這里很多人就會問, 這到底是怎么形成呢, 本質(zhì)是在三角形的三個頂點, 做線性插值的過程:

          總結(jié)

          本篇文章大概是對webgl 做了一個基本的介紹, 和帶你用幾個簡單的小例子 帶你入門了glsl 語言, 你以為webgl 就這樣嘛  那你就錯了,其實有一個texture 我是沒有講的, 后面我去專門寫一篇文章去將紋理貼圖 , 漫反射貼圖、 法線貼圖。希望你關(guān)注下我,不然找不到我了, 如果你覺得本篇文章對你有幫助的話,歡迎 點贊 、再看、收藏。我們下期再見??



          從零搭建全??梢暬笃林谱髌脚_V6.Dooring

          從零設(shè)計可視化大屏搭建引擎

          Dooring可視化搭建平臺數(shù)據(jù)源設(shè)計剖析

          可視化搭建的一些思考和實踐

          基于Koa + React + TS從零開發(fā)全棧文檔編輯器(進(jìn)階實戰(zhàn)



          創(chuàng)作不易,加個點贊、在看 支持一下哦!

          瀏覽 54
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  国内一级内射视频 | 国产精品内射视频 | 最近中文字幕中文翻译歌词 | www.蜜桃 | 日韩和亚洲的日本品牌区分米奇777788 |