通過簡單的webgl入門,已經(jīng)有了初步的認(rèn)知,大致的流程為:著色器初始化 -> 著色器程序?qū)ο?-> 控制變量 -> 繪制,為了更好的性能,后面會(huì)使用緩沖區(qū)來解決重復(fù)渲染的問題,這樣我們的頂點(diǎn)不會(huì)一個(gè)一個(gè)設(shè)置而直接會(huì)被緩存,包括后面一些動(dòng)態(tài)效果會(huì)涉及到矩陣的轉(zhuǎn)換,如平移、縮放、旋轉(zhuǎn)、復(fù)合矩陣。
?前言
大部分公司的都會(huì)有可視化的需求,但是用echarts,antv等圖表庫,雖然能快速產(chǎn)出成果,但是還是要知道他們底層其實(shí)用canvas或svg來做渲染,canvas瀏覽器原生支持,h5天然支持的接口,而svg相比矢量化,但是對大體量的點(diǎn)的處理沒有canvas好,但是可以操作dom等優(yōu)勢。canvas和svg我們一般只能做2d操作,當(dāng)canvas.getContext('webgl')我們就能獲取webgl的3d上下文,通過glsl語言操作gpu然后渲染了。理解webgl,可以明白h5的很多三維的api底層其實(shí)都是webgl實(shí)現(xiàn),包括對canvas和svg也會(huì)有新的認(rèn)知。
canvas和webgl的區(qū)別
canvas和webgl都可以做二維三維圖形的繪制。底層都會(huì)有對應(yīng)的接口獲取。cancvas一般用于二維canvas.getContext("2d")?,三維一般可以通過canvas.getContext('webgl')
窺探WebGL
理解建模
如果你有建模軟件基礎(chǔ)的話,相信3dmax、maya、su等軟件你一定不會(huì)陌生,本質(zhì)其實(shí)就是點(diǎn)、線、面來組成千變?nèi)f化的事物。打個(gè)比方球體就是無數(shù)個(gè)點(diǎn)連成線然后每三根線形成面,當(dāng)然有常見的四邊形,其實(shí)也是兩個(gè)三邊形組成,為什么不用四邊形,因?yàn)槿呅胃€(wěn)定、重心可計(jì)算、數(shù)據(jù)更容易測算。
所以核心也就是點(diǎn)、線、三角面
了解WebGL
WebGL可以簡單理解為是openGL的拓展,讓web端通過js可以有強(qiáng)大的圖形處理能力。當(dāng)然為了與顯卡做交互你必須得會(huì)glsl語言。
GLSL
glsl著色器語言最重要的就是頂點(diǎn)著色器和片元著色器。簡單理解為一個(gè)定位置一個(gè)添顏色。
簡單繪制一個(gè)點(diǎn)
webgl會(huì)有大量的重復(fù)性前置工作,也就是創(chuàng)建著色器 -> 傳入著色器源碼 -> 編譯著色器 -> 創(chuàng)建著色器程序 -> 綁定、連接、啟用著色器 -> 可以繪制了!
一般而言我們是不會(huì)重復(fù)寫這個(gè)東西,封裝好了直接調(diào)用就行。
function initShader (gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE) {
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(vertexShader, VERTEX_SHADER_SOURCE);
gl.shaderSource(fragmentShader, FRAGMENT_SHADER_SOURCE);
//編譯著色器
gl.compileShader(vertexShader);
gl.compileShader(fragmentShader);
//創(chuàng)建程序?qū)ο?br> const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.useProgram(program);
return program;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./initShader.js"></script>
</head>
<body>
<canvas id="canvas" width="300" height="400">
不支持canvas
</canvas>
</body>
<script>
const ctx = document.getElementById('canvas')
const gl = ctx.getContext('webgl')
//著色器: 通過程序用固定的渲染管線,來處理圖像的渲染,著色器分為兩種,頂點(diǎn)著色器:頂點(diǎn)理解為坐標(biāo),片元著色器:像素
//頂點(diǎn)著色器源碼
const VERTEX_SHADER_SOURCE = `
void main() {
gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
gl_PointSize = 10.0;
}
`
//片元著色器源碼
const FRAGMENT_SHADER_SOURCE = `
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
`
//創(chuàng)建著色器
const program = initShader(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE)
//執(zhí)行繪制
gl.drawArrays(gl.POINTS, 0, 1)
//gl.drawArrays(gl.LINES, 0, 1)
//gl.drawArrays(gl.TRIANGLES, 0, 1)
</script>
</html>
繪制效果如下:

相信看了上面有段代碼會(huì)有疑惑

gl_position代表坐標(biāo),vec4就一個(gè)存放個(gè)4個(gè)float的浮點(diǎn)數(shù)的容量,定義坐標(biāo), 分別對應(yīng)x、y、z、w,也就是三維坐標(biāo),但是w就等于比例縮放xyz而已,一般在開發(fā)中,我們的瀏覽器的坐標(biāo)要跟這個(gè)做個(gè)轉(zhuǎn)換對應(yīng)上,gl_POintSize是點(diǎn)的大小,注意是浮點(diǎn)數(shù)

gl_flagColor渲染的像素是紅色,是因?yàn)檫@類似于比例尺的關(guān)系需要做個(gè)轉(zhuǎn)換, (R值/255,G值/255,B值/255,A值/1) ->(1.0, 0.0, 0.0, 1.0)
繪制動(dòng)態(tài)點(diǎn)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./initShader.js"></script>
</head>
<body>
<canvas id="canvas" width="300" height="400">
不支持canvas
</canvas>
</body>
<script>
const canvas = document.getElementById('canvas')
const gl = canvas.getContext('webgl')
const VERTEX_SHADER_SOURCE = `
precision mediump float;
attribute vec2 a_Position;
attribute vec2 a_Screen_Size;
void main(){
vec2 position = (a_Position / a_Screen_Size) * 2.0 - 1.0;
position = position * vec2(1.0, -1.0);
gl_Position = vec4(position, 0, 1);
gl_PointSize = 10.0;
}
`
const FRAGMENT_SHADER_SOURCE = `
precision mediump float;
uniform vec4 u_Color;
void main() {
vec4 color = u_Color / vec4(255, 255, 255, 1);
gl_FragColor = color;
}
`
//前置工作,著色器可以渲染了!
const program = initShader(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE)
//獲取glsl的變量對應(yīng)的屬性做修改
var a_Position = gl.getAttribLocation(program, 'a_Position');
var a_Screen_Size = gl.getAttribLocation(program, 'a_Screen_Size');
var u_Color = gl.getUniformLocation(program, 'u_Color');
gl.vertexAttrib2f(a_Screen_Size, canvas.width, canvas.height); //給glsl的屬性賦值兩個(gè)浮點(diǎn)數(shù)
//給個(gè)默認(rèn)背景顏色
gl.clearColor(0, 0, 0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
//存儲(chǔ)點(diǎn)擊位置的數(shù)組。
var points = [];
canvas.addEventListener('click', e => {
var x = e.pageX;
var y = e.pageY;
var color = { r: Math.floor(Math.random() * 256), g: Math.floor(Math.random() * 256), b: Math.floor(Math.random() * 256), a: 1 };
points.push({ x: x, y: y, color: color })
gl.clearColor(0, 0, 0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
for (let i = 0; i < points.length; i++) {
var color = points[i].color;
gl.uniform4f(u_Color, color.r, color.g, color.b, color.a);
gl.vertexAttrib2f(a_Position, points[i].x, points[i].y);
gl.drawArrays(gl.POINTS, 0, 1);
}
})
</script>
</html>
vec2 position = (a_Position / a_Screen_Size) * 2.0 - 1.0; 注意這里的坐標(biāo)轉(zhuǎn)換,從canvas轉(zhuǎn)為ndc坐標(biāo),其實(shí)就是看范圍就行,[0, 1] -> [0, 2] -> [-1, 1]。上面總體的流程總結(jié)下就是,定義著色器,定義glsl著色器源碼 -> 通過api獲取canvas的信息轉(zhuǎn)換坐標(biāo)系 -> 監(jiān)聽點(diǎn)擊事件傳遞變量到glsl中 -> 通過pointer緩存 -> drawArrays繪制。但是這種方法,很明顯有大量的重復(fù)渲染,每次遍歷都要把之前渲染的重復(fù)執(zhí)行。
大致效果

總結(jié)
通過簡單的webgl入門,已經(jīng)有了初步的認(rèn)知,大致的流程為:著色器初始化 -> 著色器程序?qū)ο?-> 控制變量 -> 繪制,為了更好的性能,后面會(huì)使用緩沖區(qū)來解決重復(fù)渲染的問題,這樣我們的頂點(diǎn)不會(huì)一個(gè)一個(gè)設(shè)置而直接會(huì)被緩存,包括后面一些動(dòng)態(tài)效果會(huì)涉及到矩陣的轉(zhuǎn)換,如平移、縮放、旋轉(zhuǎn)、復(fù)合矩陣。