寫在前面
[上一篇筆記](/articles/在鼠標點擊處繪製一個點-webgl 筆記 3/) 中,我們通過 attribute 變量把鼠標點擊處的坐標信息傳遞到了頂點著色器,實現了點哪裡就在哪裡畫點的效果,但畫出來的點都是同一種顏色,如果想要動態設置點的顏色,就需要 uniform 變量了,因為attribute 變量只能用於頂點著色器,而顏色信息在片元著色器中
一、uniform 變量與 attribute 變量
uniform 與 attribute 類似,都是存儲限定符,主要的區別如下:
- attribute
只能用於頂點著色器,用來表示逐頂點的數據(每個頂點中該值不一樣),比如前面用到的 attribute vec4 a_Position
- uniform
可以用於頂點著色器,也可以用於片元著色器,用來表示一致的、不變的數據(每個頂點中該值都一樣),比如以後紋理映射中要用到的 uniform Sampler2D u_Sampler,還有我們即將使用的 uniform vec4 u_FragColor
P.S. uniform 與 attribute 還有其它區別,而且還有一個存儲限定符是 varying,這些內容都將在 GLSL ES 語法筆記中展開
二、設置點的顏色
和設置坐標類似,先聲明 uniform 變量接收顏色數據,再傳入色值,繪製出來即可
1. 片元著色器
著色的過程在片元著色器中,我們需要修改片元著色器源程序:
// 片元著色器源程序
//!!! 需要聲明浮點數精度,否則報錯 No precision specified for (float)
var fsSrc = 'precision mediump float;' +
'uniform vec4 u_FragColor;' +
'void main() {' +
'gl_FragColor = u_FragColor;' + // 設置顏色
'}';
用法和 attribute 變量完全一致,但需要特別注意的一點是:需要聲明浮點數精度,否則片元著色器會編譯失敗,並報如下錯誤:
ERROR: 0:1: '' : No precision specified for (float)
2. 設置顏色
類似於 gl.getAttribLocation 和 gl.vertexAttrib3f,這裡用的是 gl.getUniformLocation 和 gl.uniform4f:
// 設置顏色
// 獲取 uniform 變量的存儲位置
var u_FragColor = gl.getUniformLocation(glUtil.program, 'u_FragColor');
var color = item.color;
// 把色值傳遞給 uniform 變量
gl.uniform4f(u_FragColor, color.r, color.g, color.b, color.a);
注意色值 rgba 都是 0.0~1.0 的,不在此區間的數值會被自動截斷為 0.0 或者 1.0,但不報錯
3. 繪製多個點
此處是直接循環 gl.drawArrays(gl.POINTS, 0, 1); 實現的,方法不科學但能實現效果,更科學的方法需要配合 buffer,這是下一篇筆記的內容,我們暫時還沒有解鎖
var arrPos = [];
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);
// 隨機顏色
var color = {
r: Math.random(),
g: Math.random(),
b: Math.random(),
a: Math.random()
};
// 記錄坐標、顏色
arrPos.push({
x: x,
y: y,
color: color
});
// 清空 canvas
gl.clear(gl.COLOR_BUFFER_BIT);
// 在記錄的所有坐標處繪製點
arrPos.forEach(function(item) {
// 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, item.x, item.y, 0.0);
// 設置顏色
// 獲取 uniform 變量的存儲位置
var u_FragColor = gl.getUniformLocation(glUtil.program, 'u_FragColor');
var color = item.color;
// 把色值傳遞給 uniform 變量
gl.uniform4f(u_FragColor, color.r, color.g, color.b, color.a);
// 繪製點
gl.drawArrays(gl.POINTS, 0, 1);
});
});
沒有新的東西,只是一個粗暴的循環,唯一需要注意的問題是循環之前的 clear,上一篇筆記提到了這個問題:繪製完成後,顏色緩衝區會被重置,其中的內容會丟失。從效果上來看,clear 是在每次繪製之前都設置了黑色背景,如果沒有 clear,背景就會變成透明的(vec4(0.0, 0.0, 0.0, 0.0))
三、DEMO
包含上述代碼的完整的例子,請查看:http://www.ayqy.net//temp/webgl/uniform/index.html
四、總結
我們已經實現了點點點點出燦爛星空的效果,不考慮性能的話,這樣實現還是不錯的,因為下一篇筆記要介紹的 buffer 看起來更麻煩,但好處還是有的,有了 buffer 就可以一次傳遞一組數據,以後畫三角形,給三角形塗色、貼圖都會很方便
循環 draw a point 畢竟不是長久之計,總不能用一個一個的點拼三角形吧,何況還有 3D 模型,buffer 還是很有必要的
參考資料
- 《WebGL 編程指南》
暫無評論,快來發表你的看法吧