引子
JavaScript WebGL 繪製一個面之後想著可以嘗試複雜一點的了,沒想到設定顏色的時候又出現問題了。
設定顏色
在之前的示例中,都是設定單一的顏色,但每個頂點都可以擁有各自的顏色資訊。
基於繪製三角形主要有下面幾方面變化:
- 資料
- 頂點著色器
- 片元著色器
- 緩衝顏色資料
資料
顏色資料有 4 個分量:R、G、B、A 。
let colors = [
1.0, 0.0, 0.0, 1.0, // red
0.0, 1.0, 0.0, 1.0, // green
0.0, 0.0, 1.0, 1.0, // blue
];
頂點著色器
之前都是隻提供了位置變數,對於顏色需要提供額外的顏色變數進行儲存。此外還需要輸出對應的顏色到下一個階段。
const source = `
attribute vec3 vertexPos;
attribute vec4 vertexColor;
varying vec4 vColor;
void main(void){
gl_Position = vec4(vertexPos, 1);
vColor = vertexColor;
}
`;
這裡面多了一個 varying
型別的變數,這是一種頂點著色器給片斷著色器傳值的方式。
片元著色器
片元著色器接受對應的變數也要進行宣告。
const fragmentSource = `
precision highp float;
varying vec4 vColor;
void main(void){
gl_FragColor = vColor;
}
`;
這裡出現了變數,需要用 precision highp float
設定片元著色器的浮點數精度。頂點著色器有預設的精度可以不用顯式設定。
緩衝顏色資料
頂點位置資料進行了緩衝,顏色資料也要進行緩衝。
/**
* 緩衝顏色資料
* @param {*} gl WebGL 上下文
* @param {*} shaderProgram 著色器程式
* @param {*} colorData 顏色資料
*/
function setColorBuffers(gl, shaderProgram, colorData) {
// 建立空白的緩衝物件
const buffer = gl.createBuffer();
// 繫結目標
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
// WebGL 不支援直接使用 JavaScript 原始陣列型別,需要轉換
const dataFormat = new Float32Array(colorData);
// 初始化資料儲存
gl.bufferData(gl.ARRAY_BUFFER, dataFormat, gl.DYNAMIC_DRAW);
// 獲取對應資料索引,變數跟頂點著色器裡面對應
const vertexPos = gl.getAttribLocation(shaderProgram, "vertexColor");
// 解析頂點資料
gl.vertexAttribPointer(vertexPos, 4, gl.FLOAT, false, 0, 0);
// 啟用頂點屬性,頂點屬性預設是禁用的。
gl.enableVertexAttribArray(vertexPos);
}
效果
這是示例,效果如下:
發現顏色漸變發散開來了,這個是因為在光柵化過程中,轉變為畫素時對顏色進行了插值。
在程式中只定義了三個頂點的顏色,它們之間畫素的顏色會隨著畫素位置變化,相鄰畫素之間同一種單色的差值是定值。如果不想要這樣的效果,可以在片元著色器中自定義。
動態自定義示例
這是示例,片元著色器主要變化:
const fragmentSource = `
precision highp float;
varying vec4 vColor;
int findMax(float r, float g, float b) {
if (r > g && r > b) {
return 0;
}
if (g > r && g > b) {
return 1;
}
return 2;
}
void main(void){
float red = vColor.r;
float green = vColor.g;
float blue = vColor.b;
int max = findMax(red, green, blue);
vec4 finalColor = vColor;
if (max == 0) {
finalColor = vec4(1.0, 0.0, 0.0, 1.0);
}
else if (max == 1) {
finalColor = vec4(0.0, 1.0, 0.0, 1.0);
}
else if (max == 2) {
finalColor = vec4(0.0, 0.0, 1.0, 1.0);
}
gl_FragColor = finalColor;
}
`;
findMax
方法會對每個畫素的顏色的分量進行比較,將最終顏色設定為最大的一個分量。下面是效果: