寫在前面
[上一篇筆記](/articles/用 webgl 繪製一個矩形-webgl 筆記 1/) 中在頂點著色器源程序中寫死了 gl_Position,想要修改點的位置就得修改著色器源程序,這當然沒什麼意義,我們需要一種足夠靈活的方式,比如 attribute 變量
一。封裝 util
[上一篇筆記](/articles/用 webgl 繪製一個矩形-webgl 筆記 1/) 中我們為了繪製一個假矩形(一個略大的點),寫了 70 多行代碼,還只包含了錯誤檢查,兼容性處理還沒有做。每一步都在調用 WebGL 原生 API,雖然流程很清晰,但實在太麻煩,1 個點 70 行,畫兩個點 140 行可不行,所以,先動手封裝 util
開始之前,需要先注意幾個問題:
###1.getContext 的兼容性問題
getContext('webgl') 是標準方式,但在低版本瀏覽器中需要傳入不同的字符串,如下:
/**
* Creates a webgl context.
* @param {!Canvas} canvas The canvas tag to get context
* from. If one is not passed in one will be created.
* @return {!WebGLContext} The created context.
*/
var create3DContext = function(canvas, opt_attribs) {
var names = ["webgl", "experimental-webgl", "webkit-3d", "moz-webgl"];
var context = null;
for (var ii = 0; ii < names.length; ++ii) {
try {
context = canvas.getContext(names[ii], opt_attribs);
} catch(e) {}
if (context) {
break;
}
}
return context;
}
以上代碼摘自 google 團隊的 webgl-utils.js,除 create3DContext 外還提供了 requestAnimationFrame 的兼容版本,後面動畫也用得到,所以我們的 util 就直接 include webgl-utils.js 了
###2. 保留 program 對象的引用
gl.createProgram() 返回的 program 對象不僅在初始化著色器的過程中會用到,在我們即將要進行的 attribute 變量傳值中也要用,program 對象是 js 和著色器內部通信的橋樑,但問題是WebGL 沒有提供 gl.getProgram() 這樣的 getter,所以在封裝 util 時一定要保留 program 對象的引用,供外部使用,例如:
function initShaders(vsSrc, fsSrc) {
var program = _createProgram(vsSrc, fsSrc);
if (!program) {
log('Failed to create program');
return false;
}
gl.useProgram(program);
// Save program object
//!!! because there is no getter like gl.getProgram()
glUtil.program = program;
return true;
}
以上代碼摘自筆者的 gl-util.js,需要的話可以直接拿去用,接口說明如下:
// 全局變量:glUtil
return {
program: null, // runtime assignment
getContext: getContext,
initShaders: initShaders,
debug: function(onOrOff) {
if (typeof onOrOff === 'boolean') {
debug = onOrOff;
}
}
};
二。術語及 API 解釋
###1. 著色器
著色器分為頂點著色器和片元著色器:
- 頂點著色器
用來描述頂點特性(如位置、顏色等)的程序。頂點是指二維或三維空間中的一個點,比如二維或三維圖形的端點或交點
- 片元著色器
進行逐片元處理過程(如光照)的程序。片元是一個 WebGL 術語,可以簡單理解為像素(圖像的單元)
P.S. 實際上片元表示顯示在屏幕上的一個像素,還包括這個像素點的位置、顏色和其他信息
###2. 頂點著色器源程序
用 GLSL ES 寫的程序,以字符串形式傳入 WebGL 系統內部
// 頂點著色器源程序
var vsSrc = 'void main() {' +
'gl_Position = vec4(0.0, 0.0, 0.0, 1.0);' + // 設置坐標
'gl_PointSize = 200.0;' + // 設置尺寸
'}';
其中,給 gl_Position 賦值是必須的,否則著色器無法正常工作,gl_Position 是 vec4 類型的,即四維向量(齊次坐標,最後一個分量一般取 1.0)
gl_PointSize 有預設值 1.0,可以不必賦值,gl_PointSize 是 float 類型的,不接受3f(C 語言中 3f 表示浮點數 3)這樣的形式,而且只在繪製孤立點時生效(什麼叫繪製孤立點,我們後面再說)
###3. 片元著色器源程序
和頂點著色器源程序一樣,但控制的是片元著色器
// 片元著色器源程序
var fsSrc = 'void main() {' +
'gl_FragColor = vec4(1.0, 0.0, 1.0, 0.75);' + // 設置顏色
'}';
gl_FragColor 是唯一的內置變量,也是 vec4 類型,但表示 rgba 色值,rgba 的取值都是 0~1 的,不同於 CSS 中 rgb 是 0~255 的
###4. 繪製操作
繪製操作只有一個接口,但功能很強大:
// 繪製矩形(一個點,但點的尺寸略大)
gl.drawArrays(gl.POINTS, 0, 1);
API 具體信息如下:
gl.drawArrays(mode, first, count)
mode 指定繪製方式,接受常量:
---
gl.POINTS 孤立點(v0, v1...)
gl.LINES 孤立線段(v0v1, v2v3...)
gl.LINE_STRIP 連續線段(v0v1, v1v2...)
gl.LINE_LOOP 連續線段連成圈(v0v1, v1v2...vnv0)
gl.TRIANGLES 孤立三角形(v0v1v2, v3v4v5...)
gl.TRIANGLE_STRIP 連續三角帶(v0v1v2, v2v1v3, v2v3v4...),用來構造複雜模型,如球、樹
注意:第二個三角形是 v2v1v3,不是 v1v2v3,這是為了保證第二個三角形的繪製也按照逆時針順序
gl.TRIANGLE_FAN 三角扇(v0v1v2, v0v2v3, v0v3v4),也可以用來構造複雜模型,但在實際應用中不常見,因為需要求出所有三角形的公共頂點 v0
first 指定從哪個頂點開始繪製
---
0 表示從第一個開始,配合緩衝區對象使用(緩衝區對象中可以存放多個頂點信息)
count 指定需要繪製點的個數,同樣配合緩衝區對象使用
---
調用 gl.drawArray 時,頂點著色器會被執行 count 次,每次處理一個頂點,頂點著色器執行完畢後片元著色器開始執行,給片元塗色(中間還有光柵化的過程)
特別注意:頂點著色器逐頂點執行,片元著色器逐片元執行,並不是只執行一次
三。attribute 變量
在著色器源程序中聲明一個變量,然後 js 給這個變量傳值,再繪製出來,就實現了動態修改點的位置,簡單地說就是這樣
###1. 聲明 attribute 變量
// 頂點著色器源程序
var vsSrc = 'attribute vec4 a_Position;' +
'void main() {' +
'gl_Position = a_Position;' + // 設置坐標
'gl_PointSize = 200.0;' + // 設置尺寸
'}';
// 片元著色器源程序
//... 不變
類似於 C 語言的變量聲明方式,attribute 叫存儲限定符,vec4 是變量類型。變量名 a_Position 中 a_ 前綴表示變量是 attribute 變量,當然這個只是習慣,建議採用
P.S. 為什麼在頂點著色器源程序中聲明 attribute 變量,而不是在片元... 因為我們想要動態修改點的坐標,坐標信息來自頂點著色器源程序中的 gl_Position,和片元著色器沒關係
注意:只有頂點著色器才能使用 attribute 變量,片元著色器中需要使用 uniform 變量,此外還有 varying 變量,具體區別在 GLSL ES 筆記中再詳細說明
###2. 初始化著色器
直接調用 util,1 行代碼就能完成剩下的 7 件事(從 createShader 到 useProgram 的 7 個步驟)
// 1. 初始化著色器
glUtil.initShaders(vsSrc, fsSrc);
###3. 給 attribute 變量賦值
變量賦值分為 2 步,先獲取存儲位置,再賦值
// 2. 給 attribute 變量賦值
// 獲取 attribute 變量的存儲位置
var a_Position = gl.getAttribLocation(glUtil.program, 'a_Position');
if (a_Position < 0) {
console.log('Failed to get the storage location of a_Position');
return;
}
// 把頂點位置傳遞給 attribute 變量
gl.vertexAttrib3f(a_Position, 0.5, 0.0, 0.0);
###4. 繪製
// 繪製點
gl.drawArrays(gl.POINTS, 0, 1);
第一個參數傳入 gl.POINTS 表示繪製孤立點,還可以繪製線段、三角形等等,以後再說
四。DEMO
包含上述代碼的完整的例子,請查看:http://www.ayqy.net/temp/webgl/attribute/index.html
五。總結
現在我們已經可以動態修改頂點坐標了,當然,這只是很簡單的一步,後面還有:
- 如何在鼠標點擊的位置繪製一個點?
答案沒有想象的那麼簡單,canvas 和 WebGL 坐標系不同,需要轉換坐標
- 如何修改點的顏色?
用 uniform 變量就可以了
- 如何繪製多個點
循環 gl.drawArrays(gl.POINTS, 0, 1);?可以嗎?
這些都是後面的筆記要討論的問題,先不要著急
參考資料
- 《WebGL 編程指南》
暫無評論,快來發表你的看法吧