上一個程序中,只實現了靜態的單點繪製的,點的位置和顏色都是寫死在着色器中的。如果想要動態的顯示點,將交互添加進去,我們就需要實現使用 JavaScript 向着色器中傳值,這就需要使用到緩衝區。在上一個程序基礎上,實現點擊時生成隨機顏色點的效果。
一、修改着色器代碼
我們需要將着色器中創建幾個變量。着色器中一共有三種變量類型
- attribute: 只能在頂點着色器中聲明和使用,常用來存放頂點座標,頂點顏色,法線等。
- uniform: 既可以在頂點着色器中使用,也可以在片元着色器中使用,常用來存放變化矩陣,光照參數等。
- varying: 用於從頂點着色器向片元着色器中傳遞參數。
//頂點着色器源碼
var vertexShaderSource = `
//設置浮點數精度爲中等精度
precision mediump float;
//接收點在 canvas 座標系上的座標 (x, y)
attribute vec2 a_Position;
//接收 canvas 的寬高尺寸
attribute vec2 a_Screen_Size;
//接收 JavaScript 顏色值
attribute vec4 a_Color;
//將顏色值轉遞給片元着色器
varying vec4 v_Color;
void main(){
//將 canvas 座標轉換至裁剪座標系
vec2 position = (a_Position / a_Screen_Size) * 2.0 - 1.0;
position = position * vec2(1.0, -1.0);
gl_Position = vec4(position, 0, 1);
//將接收到顏色傳給 v_Color,
v_Color = a_Color;
//聲明要繪製的點的大小。
gl_PointSize = 5.0;
}
`
//片元着色器源碼
var fragmentShaderSource = `
//設置浮點數精度爲中等精度
precision mediump float;
//接收頂點着色器中傳來的顏色值
varying vec4 v_Color;
void main(){
gl_FragColor = v_Color;
}
`
二、JavaScript 操作着色器中的變量
- getAttribLocation(program, name):從 WebGLProgram 中獲取指定屬性的索引
- vertexAttrib[1234]f[v]:爲 attribute 變量賦值。
- uniform[1234][fi][v]:爲 uniform 變量賦值。
我們需要將 canvas 的長寬信息傳入着色器中,用於將 canvas 座標轉換爲 WebGL 的裁剪座標,在着色器中,我們定義了 a_Screen_Size 用與存放這個信息。在 JavaScript 中,我們首先獲取到這個變量,再通過 vertexAttrib2f 爲它賦值。
//從着色器程序中獲取 a_Screen_Size 的索引
var a_Screen_Size = gl.getAttribLocation(program, 'a_Screen_Size');
//將畫布的寬高傳入 a_Screen_Size
gl.vertexAttrib2f(a_Screen_Size, canvas.width, canvas.height);
三、使用緩衝區爲變量賦值
使用上面一種方法爲變量賦值存在一個問題,不能同時傳入多個值,比如畫三角形時,需要多個點同時參與,這種方法就行不通了。我們這個程序只需要畫點,不使用緩衝區也可以實現,但是每次畫點都需要遍歷一遍存放信息的數組,每次循環都需要在 JavaScript 中爲着色器變量賦一次值,效率比較低。爲了同時傳入多個頂點值和提高效率,我們需要使用緩衝區。
使用緩衝區需要以下步驟:
-
創建緩衝區
創建緩衝區的函數爲 createBuffer(), 我們爲位置信息和顏色信息都創建一個緩衝區。var postionBuffer = gl.createBuffer(); var colorBuffer = gl.createBuffer();
-
獲取並啓用變量
var a_Position = gl.getAttribLocation(program, 'a_Position'); var a_Color = gl.getAttribLocation(program,'a_Color'); gl.enableVertexAttribArray(a_Position); gl.enableVertexAttribArray(a_Color);
-
綁定緩衝區並設置變量從緩衝區獲取數據的規則
這一步主要涉及到兩個函數,bindBuffer 和 vertexAttribPointer。bindBuffer 用於綁定緩衝區,即指定當前使用哪一個緩衝區。vertexAttribPointer 則用於設置從緩衝區讀取數據的規則,同時,它將變量與當前緩衝區綁定起來了,這個方法有 6 個參數, 更詳細的內容可以參考 MDN。- index: 指定要修改的頂點屬性的索引
- size: 指定每個頂點屬性的組成數量, 如 a_Postion 需要兩個屬性(橫縱座標),a_Color 需要四個屬性(RGBA)
- type: 指定每個頂點屬性的數據類型, 如 a_Postion 和 a_Color 都是浮點數類型的,所以使用 gl.FLOAT
- normalized: 當轉換爲浮點數時是否應該將整數歸一化到特定的範圍
- stride: 以字節爲單位指定臉書頂點屬性開始之間的偏移量,我們數據是連續的,所以可以設置爲 0
- offset: 指定頂點屬性數組中第一部分的字節偏移量
gl.bindBuffer(gl.ARRAY_BUFFER, postionBuffer); gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0); gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); gl.vertexAttribPointer(a_Color, 4, gl.FLOAT, false, 0, 0);
-
向緩衝區中填入數據並重新渲染
之前幾步我們已經準備好了緩衝區,並將變量與緩衝區相關聯,設置好了讀取規則了,現在就需要準備緩衝區裏的數據。
首先,定義兩個空的數組用於保存位置和顏色的信息//創建保存點位置的數組 var position = []; //創建保存顏色信息的數組 var color = [];
我們再給 canvas 綁定一個點擊事件
canvas.onclick = function(e){ //將座標存入數組 position.push(e.pageX, e.pageY); //將隨機生成的顏色存入數組 color.push(Math.random(), Math.random(), Math.random(), 0); //向 postionBuffer 緩衝區中寫入數據 gl.bindBuffer(gl.ARRAY_BUFFER, postionBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(position), gl.STATIC_DRAW); //向 colorBuffer 緩衝區中寫入數據 gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(color), gl.STATIC_DRAW); //清空畫布 gl.clear(gl.COLOR_BUFFER_BIT); //重繪畫布上的點 gl.drawArrays(gl.POINTS, 0, position.length/2); }
當我們點擊時,就會將點擊位置加入 position 數組,同時生成一個隨機的顏色存入 color 數組。之後,我們就將數組中的數組傳入緩衝區中。通過 bindBuffer 選擇一個緩衝區,再通過 bufferData 將數組傳入當前緩衝區。緩衝區有了數據之後,我們再進行繪圖,調用 drawArrays 方法,其中第三個參數爲需要畫的點的個數,開始繪圖時,就會按照我們設置的規則依次從緩衝區中取點了。
四. 完整代碼
```
<canvas id="canvas"></canvas>
<script>
var canvas = document.getElementById('canvas');
var gl = canvas.getContext('webgl') || canvas.getContext("experimental-webgl");
//頂點着色器源碼
var vertexShaderSource = `
//設置浮點數精度爲中等精度
precision mediump float;
//接收點在 canvas 座標系上的座標 (x, y)
attribute vec2 a_Position;
//接收 canvas 的寬高尺寸
attribute vec2 a_Screen_Size;
//接收 JavaScript 顏色值
attribute vec4 a_Color;
//將顏色值轉遞給片元着色器
varying vec4 v_Color;
void main(){
//將 canvas 座標轉換至裁剪座標系
vec2 position = (a_Position / a_Screen_Size) * 2.0 - 1.0;
position = position * vec2(1.0, -1.0);
gl_Position = vec4(position, 0, 1);
//將接收到顏色傳給 v_Color,
v_Color = a_Color;
//聲明要繪製的點的大小。
gl_PointSize = 5.0;
}
`
//片元着色器源碼
var fragmentShaderSource = `
//設置浮點數精度爲中等精度
precision mediump float;
//接收頂點着色器中傳來的顏色值
varying vec4 v_Color;
void main(){
gl_FragColor = v_Color;
}
`
//創建着色器
var vertexShader = gl.createShader(gl.VERTEX_SHADER);
var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
//將源碼分配給着色器
gl.shaderSource(vertexShader, vertexShaderSource);
gl.shaderSource(fragmentShader, fragmentShaderSource);
//編譯着色器程序
gl.compileShader(fragmentShader);
gl.compileShader(vertexShader);
//創建着色器程序
var program = gl.createProgram();
//將頂點着色器掛載在着色器程序上
gl.attachShader(program, vertexShader);
//將片元着色器掛載在着色器程序上
gl.attachShader(program, fragmentShader);
//鏈接着色器程序
gl.linkProgram(program);
//使用剛創建好的着色器程序
gl.useProgram(program);
//設置清屏顏色
gl.clearColor(0.0, 0.0, 0.0, 1.0);
//清空畫布
gl.clear(gl.COLOR_BUFFER_BIT);
//傳入畫布長寬信息
var a_Screen_Size = gl.getAttribLocation(program, 'a_Screen_Size');
gl.vertexAttrib2f(a_Screen_Size, canvas.width, canvas.height);
var a_Position = gl.getAttribLocation(program, 'a_Position');
var a_Color = gl.getAttribLocation(program,'a_Color');
gl.enableVertexAttribArray(a_Position);
gl.enableVertexAttribArray(a_Color);
//爲點位置創建緩衝區,並啓用 a_Postion 變量
var postionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, postionBuffer);
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);
//爲顏色創建緩衝區,並啓用 a_Color 變量
var colorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.vertexAttribPointer(a_Color, 4, gl.FLOAT, false, 0, 0);
//創建保存點位置的數組
var position = [];
//創建保存顏色信息的數組
var color = [];
canvas.onclick = function(e){
//將座標存入數組
position.push(e.pageX, e.pageY);
//將隨機生成的顏色存入數組
color.push(Math.random(), Math.random(), Math.random(), 0);
//向 postionBuffer 緩衝區中寫入數據
gl.bindBuffer(gl.ARRAY_BUFFER, postionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(position), gl.STATIC_DRAW);
//向 colorBuffer 緩衝區中寫入數據
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(color), gl.STATIC_DRAW);
//清空畫布
gl.clear(gl.COLOR_BUFFER_BIT);
//重繪畫布上的點
gl.drawArrays(gl.POINTS, 0, position.length/2);
}
</script>
```