WebGL 紋理顏色原理

本文由雲+社區發表

做者:ivweb qcyhustweb

導語

WebGL繪製圖像時,往着色器中傳入顏色信息就能夠給圖形繪製出相應的顏色,如今已經知道頂點着色器和片斷着色器一塊兒決定着向顏色緩衝區寫入顏色信息並最終呈現出來,那麼這個過程是什麼樣,若是圖形的顏色須要用現有圖片來渲染那麼又該如何操做?canvas

img

顏色緩衝區

在繪製開始前,常常見到調用函數清空畫布的代碼gl.clear(gl.COLOR_BUFFER_BIT),清空畫布的繪圖區實際上就是用以前定義好的背景顏色將顏色緩衝的的顏色清除。顏色緩衝區中存放着須要顯示到畫布上的像素的顏色數據,它屬於幀緩存的一部分,與深度緩存、模板緩存等一塊兒決定着最終畫布上圖像的顯示信息。數組

能夠將顏色緩存區當作圖像顏色存儲器,在緩存區中以RGB或RGBA的格式存儲着畫布上每個像素的顏色信息,各個像素點組合起來就構成了顏色緩存的矩形陣列。這個定義看起來與圖片存儲器是很類似的,顏色緩存爲RGB或是RGBA每個通道分配存放位數,其中RGB就是顏色數據,A表示alpha也就是該像素的透明度信息,顏色佔用的位數值就是顏色深度,好比顏色深度爲24位,表示每個像素24位,通常24位的分配方案就是紅色、藍色、綠色各佔8位,若是須要透明效果的話,能夠採用32位顏色深度爲alpha通道分配8位。promise

這裏能夠總結得出,畫布上各個像素點呈現的顏色就是存放在顏色緩衝區的顏色信息所決定的,而繪製圖形的顏色緩衝區的信息又是由頂點着色器決定。要知道顏色如何渲染就要深刻分析着色器的工做過程。瀏覽器

img

圖形裝配

要繪製一個三角形,咱們是這樣定義着色器的:緩存

// 頂點着色器
const VSHADER_SOURCE =
 `attribute vec4 a_Position;
  void main() {
    gl_Position = a_Position;
  }`;

// 片斷着色器
const FSHADER_SOURCE =
 `void main() {
    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
  }`;

以後經過gl.program將頂點position座標傳入頂點着色器,這就至關於在畫布上肯定了幾個點的座標信息,這些點須要用線條鏈接起來才能構成圖形,這個由頂點座標裝配成幾何圖形的過程就叫作圖形裝配。異步

被裝配的基本圖形被稱做圖元,它包含點、線、面等基本幾何圖形。在調用WebGL的drawArrays或drawElements方法時做爲參數傳入,從而指定圖元類型。函數

一個三角形的繪製過程拆分來看就是執行三次頂點着色器,將三個點座標都傳入裝配區,根據繪製函數的圖元參數gl.TRIANGLES將三個點裝配成三角形,而後進入下一個過程——光柵化。動畫

光柵化

簡單來講,光柵化就是將圖形轉化成片元,能夠理解成一個個像素。只有將圖形轉化成像素後才能交由片斷着色器處理。webgl

光柵化結束後,WebGL執行片斷着色器。每執行一次片斷着色器就處理一個片元,將該片元的顏色寫入顏色緩衝區中,等到圖形中全部的片元處理完畢畫布上就獲得了最後的圖像。

如上面的例子,每個片元都會被執行成紅色,由這一個個紅色像素組成的三角形也就是紅色的。

若是要繪製一個多顏色三角圖形又是一個什麼過程呢?首先須要修改着色器的定義,也許能夠這樣:

// 頂點着色器
const VSHADER_SOURCE =
 `attribute vec4 a_Position;
  attribute vec4 a_Color;
  varying vec4 v_Color;
  void main() {
    gl_Position = a_Position;
    v_Color = a_Color;
  }`;

// 片斷着色器
const FSHADER_SOURCE =
 `varying vec4 v_Color;
  void main() {
    gl_FragColor = v_Color;
  }`;

向頂點着色器傳入頂點座標和顏色兩個數據,執行三次後獲得三角形三個頂點的座標和顏色,接下來經過圖元裝配獲得一個三角形的圖元,到了關鍵的光柵化這一步,該如何定義片元的顏色呢?WebGL採用一個叫作內插的過程來計算顏色的值。

以一條線爲例來解釋內插,兩個端點分別爲(1.0,0.0,0.0)和(0.0,1.0,0.0),從一端到另外一端,R的值從1.0降到0.0,G的值由0.0升到1.0,線上的全部點顏色值都這樣計算出來,實現了平滑的顏色漸變,這就是內插。

通過內插,圖形的每個片元都指定了本身的顏色,寫入顏色緩衝區後呈現出來。

紋理貼圖

若是要爲WebGL建立更加複雜更加天然的現實效果,就須要採用貼圖來將現成的圖片貼到圖形上。

圖片容器中存放的也是一個個RGB或RGBA的像素,將圖片的信息讀取後存放在紋理對象或者說紋理圖像中,紋理圖像有本身的座標系,座標中每個單元格就存放的紋理圖像的像素信息,也被稱做紋素。

img

將紋理圖像的座標轉換到畫布上圖形的座標的映射過程就是紋理映射,這個過程當中,爲圖形頂點指定了紋理座標,剩下的顏色由內插計算得出,寫入顏色緩衝區後,圖形的表面就被貼上了圖像的顏色。

img

用一個案例來實現紋理貼圖,如今要作的是:

  • 加載好須要的紋理圖像
  • 設置紋理座標
  • 對紋理進行配置
  • 片斷着色器抽出紋素並賦值給片元

在這個例子中我選擇提早加載圖片。在這裏要注意有的瀏覽器不容許訪問本地文件,能夠考慮本身搭建server或是開啓瀏覽器訪問本地文件。

function main() {
  const canvas = document.getElementById('webgl');

  const webgl = getWebGLContext(canvas);
  webgl.images = {};

  // 初始化以前先加載圖片
  loadImage([
    `src/images/0.jpeg`,
  ], webgl).then((gl) => {
    if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
      console.log('Failed to intialize shaders.');
      return;
    }

    gl.clearColor(0, 0, 0, 1);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

    const n = initVertexBuffers(gl);
    initTextures(gl, n, 0);
  });
}

loadImage的實現很簡單,用一個promise來處理異步加載圖片,傳入數組爲了以後支持多張圖片。在initVertexBuffers中建立數據buffer,將圖形頂點和紋理圖像座標一塊兒傳入着色器。

function initVertexBuffers(gl) {
  // 頂點座標和紋理圖像座標
  const vertices = new Float32Array([
    -0.3, 0.7, 0.0, 0.0, 1.0,
   -0.3, -0.7, 0.0, 0.0, 0.0,
    0.3, 0.7, 0.0, 1.0, 1.0,
    0.3, -0.7, 0.0, 1.0, 0.0,
  ]);

  const FSIZE = vertices.BYTES_PER_ELEMENT;

  const vertexBuffer = gl.createBuffer();

  gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

  const a_Position = gl.getAttribLocation(gl.program, 'a_Position');
  const a_TexCoord = gl.getAttribLocation(gl.program, 'a_TexCoord');

  gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, FSIZE * 5, 0);
  gl.enableVertexAttribArray(a_Position);

  gl.vertexAttribPointer(a_TexCoord, 2, gl.FLOAT, false, FSIZE * 5, FSIZE * 3);
  gl.enableVertexAttribArray(a_TexCoord);

  return 4;
}

而後看看最主要的initTextures,在這裏配置紋理:

function initTextures(gl, n, index) {
  // 建立紋理對象
  const texture = gl.createTexture();
  const u_Sampler = gl.getUniformLocation(gl.program, 'u_Sampler');
  const image = gl.images[index];

  // WebGL紋理座標中的縱軸方向和PNG,JPG等圖片容器的Y軸方向是反的,因此先反轉Y軸
  gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);

  // 激活紋理單元,開啓index號紋理單元
  gl.activeTexture(gl[`TEXTURE${index}`]);

  // 綁定紋理對象
  gl.bindTexture(gl.TEXTURE_2D, texture);

  // 配置紋理對象的參數
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);

  // 將紋理圖像分配給紋理對象
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);

  // 將紋理單元編號傳給着色器
  gl.uniform1i(u_Sampler, index);

  // 繪製
  gl.drawArrays(gl.TRIANGLE_STRIP, 0, n);
}

這裏又遇到兩個概念:

紋理對象配置參數 texParameteri方法用來配置紋理對象參數,函數第二個參數傳入配置參數名,第三個參數傳入配置參數值,能夠配置的參數有:

  • 伸展(gl.TEXTURE_MAX_FILTER): 繪製圖形比紋理圖像大的時候怎麼取紋素,默認值gl.LINEAR
  • 收縮(gl.TEXTURE_MIN_FILTER): 繪製圖形比紋理圖像小的時候怎麼取紋素, 默認值gl.NEAREST_MIP_LINEAR
  • 水平填充(gl.TEXTURE_WRAP_S): 定義繪製圖形水平方向如何填充,默認值gl.REPEAT
  • 垂直填充(gl.TEXTURE_WRAP_T): 定義繪製圖形垂直方向如何填充,默認值gl.REPEAT

詳細參考texParameteri

紋理單元 若是須要使用多張圖片就要管理多個紋理圖片,WebGL爲了使用多個紋理,用紋理單元來處理紋理圖像。WebGL的實現至少支持8個紋理單元,分別用gl.TEXRTRUE0,gl.TEXRTRUE1,...,gl.TEXRTRUE7來表示。

最後是着色器代碼,在調用gl.drawArrays傳入圖元類型TRIANGLE_STRIP後執行:

const VSHADER_SOURCE =
 `attribute vec4 a_Position;
  attribute vec2 a_TexCoord;
  varying vec2 v_TexCoord;
  void main() {
    gl_Position = a_Position;
    v_TexCoord = a_TexCoord;
  }`;

const FSHADER_SOURCE =
 `precision mediump float;
  uniform sampler2D u_Sampler;
  varying vec2 v_TexCoord;
  void main() {
    gl_FragColor = texture2D(u_Sampler, v_TexCoord);
  }`;

頂點着色器中傳入紋理圖像的頂點座標,將它傳遞給片斷着色器,在片斷着色器中聲明瞭一個專用於紋理對象的數據類型sampler2D,指向一個紋理單元編號(接下來解釋),着色器獲取紋素由函數texture2D完成,傳入參數紋理單元編號和紋理圖像座標。

img

多紋理實現

要使用多個紋理就要用到更多的紋理單元,多個紋理能夠組合也能夠單獨渲染,利用前面的代碼,能夠很容易擴展成一塊兒多紋理的案例,加上一些3D效果和動畫,就能夠組合成一個輪播圖片。

img

此文已由騰訊雲+社區在各渠道發佈

獲取更多新鮮技術乾貨,能夠關注咱們騰訊雲技術社區-雲加社區官方號及知乎機構號

相關文章
相關標籤/搜索