寫在前面
[上一篇筆記](/articles/attribute 變量與頂點著色器-webgl 筆記 2/) 中,我們通過 attribute 變量實現了動態設置點的位置,要在鼠標點擊處繪製點,只要拿到鼠標點擊處的坐標就好了,最大的問題是坐標轉換
一。獲取 canvas 坐標
添加事件處理器,再從事件對象身上找就好了,如下:
// webgl 是 canvas 元素的引用
webgl.addEventListener('click', function(e) {
var x = e.offsetX;
var y = e.offsetY;
}
通過事件對象的 offsetX/Y 屬性可以拿到鼠標點擊位置在 canvas 坐標系中的坐標值 (x, y),點 canvas 左上角,拿到的就是 (0, 0)
二。canvas 坐標轉 WebGL 坐標
WebGL 中原點在 canvas 中心,x 軸向右為正,y 軸向上為正,z 軸從屏幕指向臉為正,從 canvas 坐標向 WebGL 坐標轉換的方式就是簡單的計算,如下:
var x = (e.offsetX - webgl.width / 2) / (webgl.width / 2);
var y = (webgl.height / 2 - e.offsetY) / (webgl.height / 2);
簡單推導過程如下:
1. 求 x'
若點 p 在 y 軸左邊,則 x' = (width/2 - x) * -1 = x - width/2
再除以 x 軸負半軸長度得到 [0-1] 區間中的值
x' = (x - width/2) / width/2
若 p 在 y 軸右邊,則 x' = x - width/2
再除以 x 軸正半軸長度得到 [0-1] 區間中的值
x' = (x - width/2) / width/2
所以 x' = (x - width/2) / width/2
2. 求 y'
若 p 在 x 軸上邊,則 y' = height/2 - y
再除以 y 軸正半軸長度得到 [0-1] 區間中的值
y' = (height/2 - y) / height/2
若 p 在 x 軸下邊,則 y' = (y - height/2) * -1 = height/2 - y
再除以 y 軸負半軸長度得到 [0-1] 區間中的值
y' = (height/2 - y) / height/2
所以 y' = (height/2 - y) / height/2
三。繪製點
完整代碼如下:
webgl.addEventListener('click', function(e) {
// 轉換坐標
var x = (e.offsetX - webgl.width / 2) / (webgl.width / 2);
var y = (webgl.height / 2 - e.offsetY) / (webgl.height / 2);
console.log(x, y);
// 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, x, y, 0.0);
// 清空 canvas
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
// 繪製點
gl.drawArrays(gl.POINTS, 0, 1);
});
注意:每次繪製點之前我們都進行了 clear 操作,如果註釋掉 clear,會發現點擊後黑色背景和上一個點都沒了,因為 WebGL 使用的是顏色緩衝區,WebGL 系統中的繪製操作其實是在顏色緩衝區中進行繪製的,繪製結束後系統將緩衝區中的內容顯示在屏幕上,然後顏色緩衝區會被重置,其中的內容會丟失,重置後的顏色是 vec4(0.0, 0.0, 0.0, 0.0),所以 canvas 透明了
P.S. 其實上面的 clearColor 可以去掉,因為 gl.clear(gl.COLOR_BUFFER_BIT) 默認會使用上一次 gl.clearColor 指定的值,如果沒有,就使用默認顏色 vec4(0.0, 0.0, 0.0, 0.0)
四。DEMO
包含上述代碼的完整的例子,請查看:http://www.ayqy.net/temp/webgl/attribute-clickit/index.html
五。總結
目前我們實現的效果是:點哪裡在哪裡畫點,不管怎麼點,都只會出現一個點
如果想要保留歷史點,目前可以用數組記錄歷史點,然後循環 gl.drawArrays(gl.POINTS, 0, 1); 把所有的點都繪製出來,但這樣做似乎太傻了,有沒有一種能夠一次性繪製多個點的方法呢?有的,但要配合 buffer 使用,是稍微後面一點的內容
學了半天還在畫點,為了稍微有趣一點,下一篇我們介紹炫一點的內容——設置點的顏色,點點點點出燦爛星空:-)
參考資料
- 《WebGL 編程指南》
暫無評論,快來發表你的看法吧