繪制一個點
我們初步認識了 webgl,本篇主要圍繞繪制一個點的示例,逐步實作下面功能:
- 點的位置從 js 傳入著色器
- 點的大小由 js 傳入著色器
- 通過滑鼠點擊繪點
- 通過滑鼠點擊繪點,并改變點的顏色
繪制一個點(版本2)
需求
在上篇中我們在canvas中心繪制了一個點(效果如下),但這點的位置是直接寫在頂點著色器中 gl_Position = vec4(0.0, 0.0, 0.0, 1.0); ,

需求:點的位置從 js 傳入著色器
思路
將位置資訊從 js 傳入著色器有兩種方式:attribute 變數、uniform 變數
- attribute - 傳輸的是那些與頂點相關的資料
- uniform - 傳輸的是那些對于所有頂點都相同的資料

我們這里使用 attribute 變數,因為每個點都有各自的坐標
Tip:GLSL 中有三種型別的“變數”或者說資料存盤型別,每一種型別都有特定的目標和使用方法:: attributes、varyings(復雜,暫不介紹)和uniforms —— Data in WebGL
attribute
attribute 是一種著色器語言(GLSL ES)變數,用于向頂點著色器內傳輸資料,只有頂點著色器能使用它,
使用 attribute 有如下三步:
- 在頂點著色器中宣告 attribute 變數
- 在頂點著色器中將宣告的 attribute 變數賦值給 gl_Position 變數
- 向 attribute 變數傳輸資料
實作
完整代碼如下:
// point02.js
// 必須要 ;
const VSHADER_SOURCE = `
// 宣告 attribute 變數
attribute vec4 a_Position;
void main() {
// 將宣告的 attribute 變數賦值給 gl_Position 變數
gl_Position = a_Position;
gl_PointSize = 10.0;
}
`
const FSHADER_SOURCE = `
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
`
function main() {
const canvas = document.getElementById('webgl');
const gl = canvas.getContext("webgl");
// 初始化著色器
if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log('初始化著色器失敗');
return;
}
// 獲取 attribute 變數的存盤位置,如果找不到該屬性則回傳 -1,
const a_Position = gl.getAttribLocation(gl.program, 'a_Position')
if (a_Position < 0) {
console.log('找不到 a_Position 的存盤位置');
return;
}
// 為頂點 attibute 變數賦值
gl.vertexAttrib3f(a_Position, 0.0, 0.0, 0.0);
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.POINTS, 0, 1);
}
Tip:頂點著色器中必須要寫 ;
核心代碼:
// 宣告 attribute 變數
attribute vec4 a_Position;
void main() {
// 將宣告的 attribute 變數賦值給 gl_Position 變數
gl_Position = a_Position;
}
// 獲取 attribute 變數的存盤位置,如果找不到該屬性則回傳 -1,
// gl.program - 在 initShaders() 函式中創建了這個程式物件,現在只需知道它是一個引數
const a_Position = gl.getAttribLocation(gl.program, 'a_Position')
// 為頂點 attibute 變數賦值
gl.vertexAttrib3f(a_Position, 0.0, 0.0, 0.0);
Tip:習慣:所有 attribute 的變數以 a_ 開頭,所有 uniform 的變數以 u_ 開頭,
getAttribLocation
WebGLRenderingContext.getAttribLocation(program, name) 方法回傳了給定 WebGLProgram物件 中某屬性的下標指向位置,
vertexAttrib3f
vertexAttrib3f 是一系列同族方法中的一個,為頂點 attibute 變數賦值,
一系列方法指:
void gl.vertexAttrib1f(index, v0);
void gl.vertexAttrib2f(index, v0, v1);
// 將資料(v0, v1, v2) 傳給 index 指定的 attribute 變數
void gl.vertexAttrib3f(index, v0, v1, v2);
void gl.vertexAttrib4f(index, v0, v1, v2, v3);
// 矢量版本 v(vector)
void gl.vertexAttrib1fv(index, value);
void gl.vertexAttrib2fv(index, value);
void gl.vertexAttrib3fv(index, value);
void gl.vertexAttrib4fv(index, value);
- gl.vertexAttrib1f 傳1個分量,第二第三分量設定為 0.0,第四個分量設定為1.0
gl.vertexAttrib3f傳3個分量,第四個分量設定為1.0,以此類推,
矢量版本以 v 結尾,接受型別化陣列,就像這樣:
const floatArray = new Float32Array([10.0, 5.0, 2.0]);
gl.vertexAttrib3fv(a_foobar, floatArray);
繪制一個點(版本3)
需求
需求:點的大小由 js 傳入著色器 —— 在版本2的基礎上實作
實作
和版本2的實作類似:
const VSHADER_SOURCE = `
attribute vec4 a_Position;
+attribute float a_PointSize;
void main() {
// 將宣告的 attribute 變數賦值給 gl_Position 變數
gl_Position = a_Position;
- gl_PointSize = 10.0;
+ gl_PointSize = a_PointSize;
}
`
function main() {
gl.vertexAttrib3f(a_Position, 0.0, 0.0, 0.0);
+ const a_PointSize = gl.getAttribLocation(gl.program, 'a_PointSize')
+ if (a_PointSize < 0) {
+ console.log('找不到 a_PointSize 的存盤位置');
+ return;
+ }
+ // 為頂點 attibute 變數賦值
+ gl.vertexAttrib1f(a_PointSize, 10.0);
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
繪制一個點(版本4)
需求
需求:通過滑鼠點擊繪點
效果如下:

實作
完整代碼如下:
const VSHADER_SOURCE = `
// 宣告 attribute 變數
attribute vec4 a_Position;
attribute float a_PointSize;
void main() {
// 將宣告的 attribute 變數賦值給 gl_Position 變數
gl_Position = a_Position;
gl_PointSize = a_PointSize;
}
`
const FSHADER_SOURCE = `
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
`
function main() {
const canvas = document.getElementById('webgl');
const gl = canvas.getContext("webgl");
// 初始化著色器
if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log('初始化著色器失敗');
return;
}
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
const a_PointSize = gl.getAttribLocation(gl.program, 'a_PointSize')
if (a_PointSize < 0) {
console.log('找不到 a_PointSize 的存盤位置');
return;
}
// 為頂點 attibute 變數賦值
gl.vertexAttrib1f(a_PointSize, 10.0);
// 獲取 attribute 變數的存盤位置,如果找不到該屬性則回傳 -1,
const a_Position = gl.getAttribLocation(gl.program, 'a_Position')
if (a_Position < 0) {
console.log('找不到 a_Position 的存盤位置');
return;
}
// 存盤所有點
const points = [];
// 注冊點擊事件
$(canvas).click(event => {
// 將點擊的坐標轉為 webgl 坐標系統,
const rect = event.target.getBoundingClientRect();
const x = ((event.clientX - rect.left) - canvas.width / 2) / (canvas.width / 2);
const y = (canvas.height / 2 - (event.clientY - rect.top)) / (canvas.height / 2);
console.log(x, y)
// 將點保存
points.push({ x, y })
// 注:使用預設值來清慷訓沖,如果注釋這行,顏色緩沖區會被 webgl 重置為默認的透明色(0.0, 0.0, 0.0, 0.0) —— 必須
gl.clear(gl.COLOR_BUFFER_BIT);
// 繪點
points.forEach(item => {
// 為頂點 attibute 變數賦值
// 注:即使 x, y 是整數程式也沒問題
gl.vertexAttrib3f(a_Position, item.x, item.y, 0.0);
gl.drawArrays(gl.POINTS, 0, 1);
})
})
}
核心思路如下:
- 給 canvas 注冊點擊事件(這里引入jQuery)
- 點擊時將 (x, y) 轉為
webgl 坐標(怎么轉換?百度搜索,這不是重點),并將該點存入一個變數 points 中 - 回圈 points 不停繪制點
注:回圈前得通過 gl.clear 清慷訓圖區,否則canvas背景會被重置為透明色,
繪制一個點(版本5)
需求
需求:通過滑鼠點擊繪點,并改變點的顏色 —— 在版本4的基礎上實作
效果如下:

思路:顏色從硬編碼改成從 js 傳入,使用 uniform 變數,
uniform
前面我們用 attribute 向頂點著色器傳輸頂點的位置,只有頂點著色器才能使用 attribute,
對于片元著色器,需要使用 uniform 變數,
使用 uniform 有如下三步(和 attribute 類似):
- 在頂點著色器中宣告 uniform 變數
- 在頂點著色器中將宣告的 uniform 變數賦值給 gl_FragColor 變數
- 向 uniform 變數傳輸資料
實作
完整代碼:
const VSHADER_SOURCE = `
attribute vec4 a_Position;
attribute float a_PointSize;
void main() {
gl_Position = a_Position;
gl_PointSize = a_PointSize;
}
`
const FSHADER_SOURCE = `
// 片元著色器必須加上精度描述,否則瀏覽器報錯:No precision specified for (float)
precision mediump float;
// 宣告變數
uniform vec4 u_FragColor;
void main() {
gl_FragColor = u_FragColor;
}
`
function main() {
const canvas = document.getElementById('webgl');
const gl = canvas.getContext("webgl");
if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log('初始化著色器失敗');
return;
}
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
const a_PointSize = gl.getAttribLocation(gl.program, 'a_PointSize')
if (a_PointSize < 0) {
console.log('找不到 a_PointSize 的存盤位置');
return;
}
gl.vertexAttrib1f(a_PointSize, 10.0);
const a_Position = gl.getAttribLocation(gl.program, 'a_Position')
if (a_Position < 0) {
console.log('找不到 a_Position 的存盤位置');
return;
}
// 獲取 uniform 變數的存盤位置,如果找不到該屬性則回傳 -1,
const u_FragColor = gl.getUniformLocation(gl.program, 'u_FragColor')
if (u_FragColor < 0) {
console.log('找不到 u_FragColor 的存盤位置');
return;
}
const points = [];
// [0, 1)
const getColor = () => Number.parseFloat(Math.random())
$(canvas).click(event => {
const rect = event.target.getBoundingClientRect();
const x = ((event.clientX - rect.left) - canvas.width / 2) / (canvas.width / 2);
const y = (canvas.height / 2 - (event.clientY - rect.top)) / (canvas.height / 2);
// 將點的隨機顏色
points.push({ x, y, rgb: [getColor(), getColor(), getColor()] })
gl.clear(gl.COLOR_BUFFER_BIT);
points.forEach(item => {
gl.vertexAttrib3f(a_Position, item.x, item.y, 0.0);
// 給 unifrom 變數賦值
gl.uniform4f(u_FragColor, ...item.rgb, 1.0);
gl.drawArrays(gl.POINTS, 0, 1);
})
})
}
核心代碼:
const FSHADER_SOURCE = `
// 片元著色器必須加上精度描述,否則瀏覽器報錯:No precision specified for (float)
precision mediump float;
// 宣告變數
uniform vec4 u_FragColor;
void main() {
gl_FragColor = u_FragColor;
}
`
function main() {
// 獲取 uniform 變數的存盤位置,如果找不到該屬性則回傳 -1,
const u_FragColor = gl.getUniformLocation(gl.program, 'u_FragColor')
if (u_FragColor < 0) {
console.log('找不到 u_FragColor 的存盤位置');
return;
}
$(canvas).click(event => {
...
points.forEach(item => {
...
// 給 unifrom 變數賦值
gl.uniform4f(u_FragColor, ...item.rgb, 1.0);
gl.drawArrays(gl.POINTS, 0, 1);
})
})
}
注:片元著色器必須加上精度描述(例如:precision mediump float;),否則瀏覽器報錯:No precision specified for (float)
getUniformLocation
getUniformLocation 與 getAttribLocation 類似,只是這里回傳 uniform 變數的位置
uniform4f
有了 uniform 變數的存盤地址,就可以使用 uniform4f(和 vertexAttrib3f 很類似) 向變數中寫入資料,
出處:https://www.cnblogs.com/pengjiali/p/17168064.html
本文著作權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連接,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/545481.html
標籤:JavaScript
下一篇:第124篇: 期約Promise
