開發WebGL的第一步

做者:Aral Roca

翻譯:瘋狂的技術宅前端

原文:https://aralroca.com/blog/fir...react

未經容許嚴禁轉載git

在本文中,咱們將瞭解什麼是 WebGL,以及如何經過與 GPU 進行對話來繪製「三角形」。儘管有更好的方法來實現本文中的例子,例如用具備 2d 上下文的 canvas 甚至能夠用 CSS,但咱們要從 WebGL 開始。就像 「hello world」 同樣,瞭解它是如何工做的。程序員

image.png

什麼是WebGL?

WebGL 的字面定義是 Web Graphics Library(Web圖形庫)。但這並不意味着咱們能夠經過 3D 庫提供的一個好用的 API 去指揮電腦:「在這裏放個茶杯,在這裏放攝像機,在這裏畫一個字符等」。github

它屬於低級 API,能夠將 vertices(頂點)轉換爲pixels(像素)。你能夠把 WebGL 理解爲柵格化引擎。它基於 OpenGL ES 3.0 圖形 API。web

image.png

網上現有的 3d 庫(例如THREE.jsBabylon.js)使用圖中最下面的 WebGL。他們須要一種可以與 GPU 通信來告知繪製內容的方法。面試

固然也能夠經過 THREE.js 的 THREE.Triangle 直接解決。可是本文的目的是瞭解其在內部的工做方式,即這些 3d 庫時如何經過 WebGL 與 GPU 通訊的。咱們要在沒有任何 3D 庫的幫助下渲染出一個三角形。canvas

image.png

建立 WebGL 畫布

在繪製三角形以前,須要先定義 WebGL 渲染的三角形的區域。segmentfault

咱們將使用 HTML5 的元素 canvas,將上下文設置爲 webgl2數組

import { useRef, useEffect } from 'preact/hooks'

export default function Triangle() {
  const canvas = useRef()

  useEffect(() => {
    const bgColor = [0.47, 0.7, 0.78, 1] // r,g,b,a as 0-1
    const gl = canvas.current.getContext('webgl2') // WebGL 2.0

    gl.clearColor(bgColor) // 設置 canvas 的背景顏色
    gl.clear(gl.DEPTH_BUFFER_BIT | gl.COLOR_BUFFER_BIT) // clear buffers
    // @todo: 渲染三角形...
  }, [])

  return <canvas style={{ width: '100vw', height: '100vh' }} ref={canvas} />
}

clearColor 方法用 RGBA(取值範圍:0 到 1)設置畫布的背景色。

clear 方法用來把緩衝區初始化爲預設值。其對應的常數值將取決於你的 GPU。

建立 canvas 後,就能夠用 WebGL 渲染三角形了。

頂點座標

首先要知道咱們所使用的這些向量的取值範圍是 -1 至 1。

畫布的座標範圍:

image.png

  • (0,0):中心
  • (1,1):右上
  • (1,-1):右下
  • (-1,1):左上方
  • (-1,-1):左下

要繪製的三角形的三個頂點爲(-1,-1)(0,1)(1,-1)。把三角的頂點座標存儲到數組中::

image.png

const coordinates = [-1, -1, 0, 1, 1, -1]

GLSL 和着色器

着色器是計算機圖形學中的一種程序,能夠高度靈活地計算渲染效果。這些着色器用相似於 C 或 C++ 的 OpenGL ES 着色語言(GLSL ES)編寫,並在GPU上編碼並運行。

image.png

每一個 WebGL 程序都由兩個着色器函數組成; 頂點着色器(vertex shader)片斷着色器(fragment shader)。幾乎全部的 WebGL API 都以不一樣的方式運行這兩個函數。

頂點着色器

頂點着色器的工做是計算頂點的位置。有了這個結果(gl_Position)GPU 就在視口上定位了點、線和三角形。因此要先建立這個頂點着色器:

const vertexShader = `#version 300 es
  precision mediump float;
  in vec2 position;

  void main () {
      gl_Position = vec4(position.x, position.y, 0.0, 1.0); // x,y,z,w
  }
`

能夠將其做爲模板字符串保存在咱們的 JavaScript 代碼中。

第一行( #version 300 es)說明了正在使用的 GLSL 版本。

第二行( precision mediump float;)肯定 GPU 用於計算浮點數的精度。可用的選項有 highpmediumplowp),可是某些系統不支持 highp

在第三行(in vec2 position;)爲 GPU 定義了兩個二維 (X,Y) 的輸入變量。三角形的每一個向量都是二維的。

初始化後在程序啓動時調用 main 函數(和 C/C++ 語言同樣)。 GPU 將經過將當前頂點的位置保存到 gl_Position 來運行其內容(gl_Position = vec4(position.x, position.y, 0.0, 1.0);)。第一個和第二個參數是 vec2 的位置 xy。第三個參數是 z 軸,在本例中爲 0.0,由於咱們是在2D而非3D中建立幾何體。最後一個參數是 w,默認狀況下應將其設置爲 1.0

GLSL 識別並在內部使用 gl_Position 的值。

建立着色器後,應對其進行編譯:

const vs = gl.createShader(gl.VERTEX_SHADER)

gl.shaderSource(vs, vertexShader)
gl.compileShader(vs)

// Catch some possible errors on vertex shader
if (!gl.getShaderParameter(vs, gl.COMPILE_STATUS)) {
  console.error(gl.getShaderInfoLog(vs))
}

片斷着色器

「片斷着色器」在「頂點着色器」以後執行。着色器的工做是計算與每一個位置對應的像素點的顏色。

咱們用相同的顏色填充這個三角形:

const fragmentShader = `#version 300 es
  precision mediump float;
  out vec4 color;

  void main () {
      color = vec4(0.7, 0.89, 0.98, 1.0); // r,g,b,a
  }
`
const fs = gl.createShader(gl.FRAGMENT_SHADER)

gl.shaderSource(fs, fragmentShader)
gl.compileShader(fs)

// 在片斷着色器上捕獲一些可能出現的錯誤
if (!gl.getShaderParameter(fs, gl.COMPILE_STATUS)) {
  console.error(gl.getShaderInfoLog(fs))
}

儘管在這裏返回的 vect4 是指每一個像素的顏色,但其語法與上一個很是類似。因爲要用 rgba(179, 229, 252, 1) 填充三角形,因此要把每一個 RGB 數字除以 255。

從着色器建立程序

編譯好着色器後,須要建立要運行 GPU 的程序,同時添加兩個着色器。

const program = gl.createProgram()
gl.attachShader(program, vs) // 附加頂點着色器
gl.attachShader(program, fs) // 附加片斷着色器
gl.linkProgram(program) // 將兩個着色器連接在一塊兒
gl.useProgram(program) // 使用建立的程序

// 捕獲在程序中可能出現的錯誤
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
  console.error(gl.getProgramInfoLog(program))
}

建立緩衝區

咱們將使用緩衝區把內存分配給 GPU,並把內存綁定到用於 CPU-GPU 通訊的通道。用此通道將三角形座標發送到GPU。

// 爲gpu分配內存
const buffer = gl.createBuffer()

// 將此內存綁定到通道
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)

// 使用此通道將數據發送到GPU(三角形座標)
gl.bufferData(
  gl.ARRAY_BUFFER,
  new Float32Array(coordinates),
  // 在例子中是一個靜態三角形,
  // 因此最好說明咱們要如何使用數據,
  // 以便 WebGL 能夠優化某些內容。
  gl.STATIC_DRAW
)

// 發送數據後釋放內存,避免內存泄漏問題
gl.bindBuffer(gl.ARRAY_BUFFER, null)

image.png

把數據從 CPU 連接到 GPU

在咱們的頂點着色器中定義了一個名爲 position 的輸入變量。可是還沒有指定此變量應採用咱們要經過緩衝區的值。必須經過如下方式代表它:

const position = gl.getAttribLocation(program, 'position')
gl.enableVertexAttribArray(position)
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
gl.vertexAttribPointer(
  position, //頂點屬性的位置
  2, // 維度 - 2D
  gl.FLOAT, // 要發送到 GPU 的數據類型
  gl.FALSE, // 是否應將數據標準化
  0, // 步長
  0 // 偏移量
)

繪製三角形

一旦爲三角形建立了帶有着色器的程序,並建立了將數據從CPU發送到GPU的連接緩衝區以後,最後就能夠告訴 GPU 渲染三角形了。

image.png

gl.drawArrays(
  gl.TRIANGLES, // 基本類型
  0, // 向量點數組中的起始索引
  3 // 要渲染的頂點數
)

這個方法用來從數組數據渲染圖元。圖元能夠是點、線或三角形。在這裏指定 gl.TRIANGLES

結論

用 WebGL 只能繪製三角形、直線或點,由於它只能柵格化,因此只能進行向量能夠執行的操做。這意味着 WebGL 從概念上講是簡單的,而過程卻至關複雜,並且根據你要開發的內容會變得愈來愈複雜。光柵化 2D 三角形並不像在 3D 遊戲中使用紋理、變化,變換那樣。

173382ede7319973.gif


本文首發微信公衆號:前端先鋒

歡迎掃描二維碼關注公衆號,天天都給你推送新鮮的前端技術文章

歡迎掃描二維碼關注公衆號,天天都給你推送新鮮的前端技術文章

歡迎繼續閱讀本專欄其它高贊文章:


相關文章
相關標籤/搜索