做者:Aral Roca翻譯:瘋狂的技術宅前端
原文:https://aralroca.com/blog/fir...react
未經容許嚴禁轉載git
在本文中,咱們將瞭解什麼是 WebGL,以及如何經過與 GPU 進行對話來繪製「三角形」。儘管有更好的方法來實現本文中的例子,例如用具備 2d 上下文的 canvas 甚至能夠用 CSS,但咱們要從 WebGL 開始。就像 「hello world」 同樣,瞭解它是如何工做的。程序員
WebGL 的字面定義是 Web Graphics Library(Web圖形庫)。但這並不意味着咱們能夠經過 3D 庫提供的一個好用的 API 去指揮電腦:「在這裏放個茶杯,在這裏放攝像機,在這裏畫一個字符等」。github
它屬於低級 API,能夠將 vertices(頂點)轉換爲pixels(像素)。你能夠把 WebGL 理解爲柵格化引擎。它基於 OpenGL ES 3.0 圖形 API。web
網上現有的 3d 庫(例如THREE.js 或 Babylon.js)使用圖中最下面的 WebGL。他們須要一種可以與 GPU 通信來告知繪製內容的方法。面試
固然也能夠經過 THREE.js 的 THREE.Triangle
直接解決。可是本文的目的是瞭解其在內部的工做方式,即這些 3d 庫時如何經過 WebGL 與 GPU 通訊的。咱們要在沒有任何 3D 庫的幫助下渲染出一個三角形。canvas
在繪製三角形以前,須要先定義 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。
畫布的座標範圍:
要繪製的三角形的三個頂點爲(-1,-1),(0,1)和(1,-1)。把三角的頂點座標存儲到數組中::
const coordinates = [-1, -1, 0, 1, 1, -1]
着色器是計算機圖形學中的一種程序,能夠高度靈活地計算渲染效果。這些着色器用相似於 C 或 C++ 的 OpenGL ES 着色語言(GLSL ES)編寫,並在GPU上編碼並運行。
每一個 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 用於計算浮點數的精度。可用的選項有 highp
, mediump
和 lowp
),可是某些系統不支持 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
的位置 x
和 y
。第三個參數是 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)
在咱們的頂點着色器中定義了一個名爲 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 渲染三角形了。
gl.drawArrays( gl.TRIANGLES, // 基本類型 0, // 向量點數組中的起始索引 3 // 要渲染的頂點數 )
這個方法用來從數組數據渲染圖元。圖元能夠是點、線或三角形。在這裏指定 gl.TRIANGLES
。
用 WebGL 只能繪製三角形、直線或點,由於它只能柵格化,因此只能進行向量能夠執行的操做。這意味着 WebGL 從概念上講是簡單的,而過程卻至關複雜,並且根據你要開發的內容會變得愈來愈複雜。光柵化 2D 三角形並不像在 3D 遊戲中使用紋理、變化,變換那樣。