WebGL學習系列-第一個程式
前言
本篇學習第一個WebGL程式——畫一個點,通過此程式來理解WebGL程式的結構,這是所有後續知識的開端。
畫一個點
先看一下效果圖:
為了畫這麼一個點,在WebGL可不太簡單,它會涉及到WebGL上下文以及著色器的概念,不要著急,咱們慢慢來理解。
WebGL上下文
學過Canvas的同學應該都知道,想要在瀏覽器中使用Canvas畫圖,需要先取得一個上下文,就像建立一個場景一樣,有了場景才可以繪製。在WebGL中,想要進行繪製,一樣需要先獲取一個上下文,而且跟Canvas 2D開發上下文的獲取非常的類似。
1、首先,要有一個canvas dom元素
<canvas id="webgl" width="400" height="400">
您的瀏覽器不支援canvas,建議使用Chrome瀏覽器
</canvas>
2、獲取上一步得到的 canvas dom元素,然後獲取WebGL上下文
var canvas = document.getElementById('webgl');
// 獲取WebGL上下文
function createWebGLContext(canvas){
var names = ["experimental-webgl", "webgl" , "webkit-3d", "moz-webgl"];
var webglContext = null;
for (var i = 0; i < names.length; i++) {
try {
webglContext = canvas.getContext(names[i]);
if(webglContext){
break;
}
} catch(e) {}
}
return webglContext;
}
考慮到相容性,所以我們嘗試多種獲取WebGL上下文的方式,只要任何一種獲取成功即可返回WebGL上下文。
背景清除
學習WebGL開發的時候,有個非常重要的概念就是清除重繪。打個比方,我們想要在介面上畫兩個矩形,先畫第一個,然後隔5秒後再畫第二個,最終介面上顯示2個矩形。那麼正確的做法是先繪製第一個矩形,然後使用一個5秒的定時器,5秒後清空之前繪製的所有內容,然後繪製兩個矩形。說白了,就是你所看到的東西是一次性繪製出來的,而不能先繪製一點,隔一會再繪製一點,每一次繪製都要進行整個畫面的重繪。而在重繪的時候,一般我們都會設定一個重繪的預設背景顏色,設定一次即可。
// 這裡指定預設背景色為黑色,不透明
context.clearColor(0.0, 0.0, 0.0, 1.0);
設定完預設背景色之後,在每一次繪製之前,我們手動清除繪製區域,然後重繪整個畫面。
// 清空
context.clear(context.COLOR_BUFFER_BIT);
注意到這裡我們使用 context.COLOR_BUFFER_BIT,其實,我們每一次繪製的時候,可能要畫非常多的圖形,不是說我們畫一個圖形就立馬在瀏覽器上顯示的,那樣很可能出現閃屏,所以我們繪製其實是繪製在稱為顏色緩衝區的一塊記憶體空間來的,等全部繪製好之後,再一次性重新整理到瀏覽器展現,這樣才不會閃屏。除了COLOR_BUFFER_BIT(顏色緩衝區),以後我們還會接觸context.DEPTH_BUFFER_BIT(深度緩衝區),後續篇章再解釋。
繪製
有了上下文,知道了繪製的方法,接下來我們就要繪製了,本篇我們要繪製一個點,為了繪製一個點,我們要有繪製點的api,要知道點的位置,大小和顏色。
我們使用 context.drawArrays(context.POINTS, 0, 1); 來繪製一個點,drawArrays可以用於繪製點、線段、扇形等各類形狀,引數簡要介紹如下:
繪製基本圖形:
drawArrays(mode , first , count)
mode: 繪製的形狀,用於決定點是怎麼連線成圖形的
first: 頂點開始索引
count: 使用多少個頂點進行繪製
此api能夠繪製的基本圖形如下所示:
有了繪製的api還不夠,我們發現沒有地方指定點的位置、大小、顏色之類的屬性,這對於新學習的朋友一定感到很迷惑,到現在還沒有看到可以設定點屬性的地方,這也是WebGL比較麻煩,但也是非常精華的一部分,這一塊實際上是由著色器來完成的。
先來看一張圖:
在WebGL中,有兩類著色器,分別為頂點著色器和片元著色器,它們的作用上圖已經表述得很清楚了,著色器其實就是一段程式碼,然後會被底層不斷的呼叫,從而獲取繪製所需要的點的一些資訊。為了使用著色器,需要建立一個program物件,program物件內部再繫結頂點著色器和片元著色器物件,而著色器物件又是需要我們建立的,建立後指定程式碼片段字串即可,最後在context中,使用這個program即可,drawArrays介面內部會自行跟program中的著色器通訊對接。
再來看一張頂點著色器的示意圖:
再對應到程式碼中就很容易理解了,片元著色器的使用流程也是一樣的。
本示例中我們的頂點著色器程式碼片段如下:
// 頂點著色器程式碼(決定頂點在哪裡、大小、顏色)
var VSHADER_SOURCE =
'void main() {\n' +
' gl_Position = vec4(0.0, 0.0, 0.0, 1.0);\n' + // 設定頂點的位置
' gl_PointSize = 10.0;\n' + // 設定頂點的大小
'}\n';
注意,著色器的程式碼片段是以字串形式存在 的,而且像其他語言一樣有個main函式,頂點著色器有幾個內建變數,目前我們使用了gl_Position 和 gl_PointSize ,分別表示頂點的位置和大小,而且我們在這裡固化了變數的值,以後再來研究怎麼實現動態變數,現在你要知道,當我們執行drawArrays的時候,底層會執行頂點著色器的程式碼,獲取點的位置和大小。
片元著色器的程式碼如下:
var FSHADER_SOURCE =
'void main() {\n' +
' gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n' + // 設定頂點的顏色
'}\n';`````````
在片元著色器中,我們使用了內部屬性 gl_FragColor,用於表示畫素的顏色值,此程式碼片段在真正執行時,對於每一個畫素,都會被呼叫一次,所以稱之為片元著色器也挺符合的。
小結
經過上面的分析,我們發現在WebGL中繪製一個點確實不太容易,但是當理清了各個涉及到的知識點之後,畫其他東西原理都差不多的,最主要還是要理解著色器的概念,這只是第一篇,往後隨著不斷學習,不斷加深對著色器的理解。
原始碼
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>第一個WebGL程式-畫一個點</title>
</head>
<body onload="main()">
<canvas id="webgl" width="400" height="400">
您的瀏覽器不支援canvas,建議使用Chrome瀏覽器
</canvas>
<script>
// 主程式入口
function main(){
var canvas = document.getElementById('webgl');
var context = createWebGLContext(canvas);
var program = createProgram(context , VSHADER_SOURCE ,FSHADER_SOURCE);
context.program = program;
context.useProgram(program);
// 每一次重繪時的背景色
context.clearColor(0.0, 0.0, 0.0, 1.0);
// 清除 <canvas>
context.clear(context.COLOR_BUFFER_BIT);
// 畫一個點
context.drawArrays(context.POINTS, 0, 1);
}
// 獲取WebGL上下文
function createWebGLContext(canvas){
var names = ["experimental-webgl", "webgl" , "webkit-3d", "mozwebgl"];
var webglContext = null;
for (var i = 0; i < names.length; i++) {
try {
webglContext = canvas.getContext(names[i]);
if(webglContext){
break;
}
} catch(e) {}
}
return webglContext;
}
// 建立一個program(相當於著色器的上下文)
function createProgram(context, vshader, fshader){
var vertexShader = loadShader(context, context.VERTEX_SHADER,vshader);
var fragmentShader = loadShader(context,context.FRAGMENT_SHADER,fshader);
var program = context.createProgram();
context.attachShader(program, vertexShader);
context.attachShader(program, fragmentShader);
context.linkProgram(program);
return program;
}
function loadShader(context, type, source){
var shader = context.createShader(type);
context.shaderSource(shader, source);
context.compileShader(shader);
return shader;
}
// 頂點著色器程式碼(決定頂在哪裡,大小)
var VSHADER_SOURCE =
'void main() {\n' +
' gl_Position = vec4(0.0, 0.0, 0.0, 1.0);\n' + // 設定頂點的位置
' gl_PointSize = 10.0;\n' + // 設定頂點的大小
'}\n';
// 片元著色器程式碼(給畫素上色)
var FSHADER_SOURCE =
'void main() {\n' +
' gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n' + // 設定頂點的顏色
'}\n';
</script>
</body>
</html>