前言
這是webgl入門的第2節,內容包含weblg的座標系和動態設定圖形樣式。
1. webgl座標系統
由於webgl處理的是三維圖形,所以它使用三維座標系統。具有x軸、y軸、z軸。當你面對螢幕的時候,X軸是水平的(正方形向右),Y軸是垂直的(正方向向上(原文中有錯)),而Z軸垂直於螢幕(正方向向外) 這套座標系又叫右手座標系。
如圖所示,webgl的座標系(原點在畫布中間)跟canvas的座標系(原點在畫布左上角)是不同的。使用canvas顯示webgl的時候,需要將webgl的座標轉換成canvas座標。
- canvas中心點在:(0.0,0.0,0.0)
- canvas的上邊緣和下邊緣:(0.0,1.0,0.0)和(0.0,-1.0,0.0)
- canvas的左邊緣和右邊緣:(-1.0,0.0,0.0)和(1.0,0.0,0.0)
原書關於這裡的描述是錯誤的,已訂正
你可以點選這裡通過修改gl_Position來檢視效果。
2.繪製一個點(版本2)
這一節我們將討論如何在Javascript和著色器之間傳輸資料。
在這一節中,webgl程式可以將頂點位置座標從js傳到著色器程式中我,然後在對應的位置將點繪製出來。
2.1 attribute變數
我們的目標是將位置資訊從js傳到著色器,有2種方式可以做到這點:attribute變數和uniform變數。使用哪個變數取決於要傳遞的資料本身。
- attribute變數傳輸的是與頂點有關的資料
- uniform變數傳輸的是那些對於所有頂點都相同的資料
attribute變數是GLSL ES變數,被用來從外部向頂點著色器內部傳遞資料,只有頂點著色器能使用它
使用attribute變數,程式應該包含如下步驟:
- 在頂點著色器中,宣告attribute變數
- 將attribute變數賦值給gl_Position變數
- 向attribute變數傳輸資料
html程式碼(跟之前一樣):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>webgl學習</title>
</head>
<body>
<canvas id="canvas" width="400" height="400"></canvas>
<!-- 引入幾個webgl幫助函式,這裡的程式碼很簡單,你可以直接點選檢視 -->
<script src="https://mycode04-1252305175.cos.ap-guangzhou.myqcloud.com/webgl-lib/lib/webgl-utils.js"></script>
<script src="https://mycode04-1252305175.cos.ap-guangzhou.myqcloud.com/webgl-lib/lib/webgl-debug.js"></script>
<script src="https://mycode04-1252305175.cos.ap-guangzhou.myqcloud.com/webgl-lib/lib/cuon-utils.js"></script>
<script src="./index.js"></script>
</body>
</html>
複製程式碼
javascript程式碼(頂點著色器不一樣了):
// 頂點著色器 注意這裡的\n不能省略。否則不符合語法
var VSHADER_SOURCE = `
attribute vec4 a_Position\n;
void main(){\n
gl_Position = a_Position;\n
gl_PointSize = 10.0;\n
}\n
`
// 片段(片元)著色器
var FSHADER_SOURCE = `
void main(){\n
gl_FragColor = vec4(1.0,0.0,0.0,1.0);\n
}\n
`
function main () {
// 獲取canvas
var canvas = document.getElementById('canvas')
// 獲取webgl繪圖上下文
var gl = canvas.getContext('webgl')
if (!gl) {
console.log('Failed to get the rendering context')
retun
}
// 初始化著色器函式
if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log('設定著色器失敗')
return
}
// 獲取attribute的儲存位置
var a_Position = gl.getAttribLocation(gl.program, 'a_Position')
if (a_Position < 0) {
console.log('Failed to get the storage of a_Position')
return
}
// 將頂點位置傳輸給attribute變數
gl.vertexAttrib3f(a_Position, 0.0, 0.0, 0.0)
// 指定清空canvas的顏色
gl.clearColor(0.0, 0.0, 0.0, 1.0)
// 清空canvas
gl.clear(gl.COLOR_BUFFER_BIT)
// 畫一個點
gl.drawArrays(gl.POINTS, 0, 1)
}
window.onload = function () {
main()
}
複製程式碼
3. 程式碼解析
3.1 宣告attribute變數
我們在著色器中宣告的attribute變數
attribute vec4 a_Position;
在這一行中,關鍵詞attribute被稱為儲存限定符,表示接下來的變數是一個attribute變數。attribute變數必須宣告為全域性變數,資料將從著色器外部傳遞給該變數。
變數宣告的格式如下: <儲存限定符><型別><變數名>
然後我們將attribute變數賦值給gl_Position
gl_Position = a_Position;
複製程式碼
3.2 獲取attribute變數的儲存位置
每一個變數都有一個儲存地址,我們通過儲存地址向變數傳輸資料。為了向attribute變數傳輸資料,我們要先獲取其儲存地址。
// 獲取attribute的儲存位置
var a_Position = gl.getAttribLocation(gl.program, 'a_Position')
複製程式碼
方法的第一個引數是程式物件,它包含頂點著色器和片元著色器。gl.program是怎麼來的,你可以檢視html程式碼中引入的幫助函式cuon-utils.js
方法返回值是attribute變數的儲存地址,這個地址被儲存在js變數中。為了便於理解,本書儲存變色器地址的js變數名跟著色器變數名一致(都是a_Position)。
gl.getAttribLocation(program,name)函式規範如下:
型別 | 引數 | 含義 |
---|---|---|
引數 | program | 指定包含頂點著色器和片元著色器的程式物件 |
引數 | name | 指定想要獲取儲存地址的attribute變數名稱 |
返回值 | 大於等於0 | 變數地址 |
返回值 | -1 | 指定的attribute變數不存在,或其命名具有gl_或者webgl_字首 |
錯誤 | INVALID_OPERATION | 程式物件未能成功連線 |
INVALID_VALUE | name引數的長度大於attribute變數名的最大程度(256個位元組) |
3.3 向attribute變數賦值
// 將頂點位置傳輸給attribute變數
gl.vertexAttrib3f(a_Position, 0.0, 0.0, 0.0)
複製程式碼
gl.vertexAttrib3f(location,v0,v1,v2)的用法
型別 | 引數 | 含義 |
---|---|---|
引數 | location | 指定要修改的attribute變數的儲存位置 |
v0 | 第一個分量的值(x的值) | |
v1 | 第二個分量的值(y的值) | |
v2 | 第三個分量的值(z的值) | |
返回值 | 無 | |
錯誤 | INVALID_OPERATION | 沒有當前program物件 |
INVALID_VALUE | location大於等於attribute變數的最大數目預設為8. |
gl.vertexAttrib3f() 同族函式
gl.vertexAttrib3f
是一系列同族函式中的一個,該系列函式的任務就是象js頂點著色器中的attribute傳值。
- gl.vertexAttrib1f(location,v0)
- gl.vertexAttrib2f(location,v0,v1)
- gl.vertexAttrib3f(location,v0,v1,v2)
- gl.vertexAttrib4f(location,v0,v1,v2,v3)
v0為x座標,v1為y座標,v2為z座標,v3預設填1.0就可以。
3.4 webgl函式命名規範
<函式名稱><引數個數><引數型別>
4 通過滑鼠點選繪點
這一節我們將靈活的擴充js傳輸資料到頂點著色器的能力:在滑鼠點選的位置上把點繪製出來。
html程式碼(跟之前相同)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>webgl學習</title>
</head>
<body>
<canvas id="canvas" width="400" height="400"></canvas>
<!-- 引入幾個webgl幫助函式,這裡的程式碼很簡單,你可以直接點選檢視 -->
<script src="https://mycode04-1252305175.cos.ap-guangzhou.myqcloud.com/webgl-lib/lib/webgl-utils.js"></script>
<script src="https://mycode04-1252305175.cos.ap-guangzhou.myqcloud.com/webgl-lib/lib/webgl-debug.js"></script>
<script src="https://mycode04-1252305175.cos.ap-guangzhou.myqcloud.com/webgl-lib/lib/cuon-utils.js"></script>
<script src="./index.js"></script>
</body>
</html>
複製程式碼
js程式碼
// 頂點著色器 注意這裡的\n不能省略。否則不符合語法
var VSHADER_SOURCE = `
attribute vec4 a_Position\n;
void main(){\n
gl_Position = a_Position;\n
gl_PointSize = 10.0;\n
}\n
`
// 片段(片元)著色器
var FSHADER_SOURCE = `
void main(){\n
gl_FragColor = vec4(1.0,0.0,0.0,1.0);\n
}\n
`
function main () {
// 獲取canvas
var canvas = document.getElementById('canvas')
// 獲取webgl繪圖上下文
var gl = canvas.getContext('webgl')
if (!gl) {
console.log('Failed to get the rendering context')
retun
}
// 初始化著色器函式
if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log('設定著色器失敗')
return
}
// 獲取attribute的儲存位置
var a_Position = gl.getAttribLocation(gl.program, 'a_Position')
if (a_Position < 0) {
console.log('Failed to get the storage of a_Position')
return
}
// 註冊滑鼠點選事件響應函式
canvas.onmousedown = function (ev) {
click(ev, gl, canvas, a_Position)
}
// 指定清空canvas的顏色
gl.clearColor(0.0, 0.0, 0.0, 1.0)
// 清空canvas
gl.clear(gl.COLOR_BUFFER_BIT)
// 滑鼠的點選位置陣列
var g_pointArr = []
// 滑鼠點選處理函式
function click (ev, gl, canvas, a_Position) {
var x = ev.clientX
var y = ev.clientY
var rect = ev.target.getBoundingClientRect()
// 計算點選位置在webgl系統中的x座標(原書中這裡有錯誤)
var x = (x - rect.left - canvas.width / 2) / (canvas.width / 2)
// 計算點選位置在webgl系統中的y座標(原書中有錯誤)
var y = (canvas.height / 2 - (y - rect.top)) / (canvas.height / 2)
// 將點x和y的座標存入陣列
g_pointArr.push(x)
g_pointArr.push(y)
// 清空canvas
gl.clear(gl.COLOR_BUFFER_BIT)
var len = g_pointArr.length
for (var i = 0; i < len; i += 2) {
gl.vertexAttrib3f(a_Position, g_pointArr[i], g_pointArr[i + 1], 0.0)
gl.drawArrays(gl.POINTS, 0, 1)
}
}
}
window.onload = function () {
main()
}
複製程式碼
5 函式解析
5.1 註冊滑鼠響應函式
這跟我們普通的註冊dom事件是一樣的。
canvas.onmousedown = function (ev) {
click(ev, gl, canvas, a_Position)
}
複製程式碼
5.2 計算滑鼠點選時的座標
這一段程式中比較難理解的可能就是這點了。其實很簡單。
var x = ev.clientX
var y = ev.clientY
var rect = ev.target.getBoundingClientRect()
// 計算點選位置在webgl系統中的x座標(原書中這裡有錯誤)
var x = (x - rect.left - canvas.width / 2) / (canvas.width / 2)
// 計算點選位置在webgl系統中的y座標(原書中有錯誤)
var y = (canvas.height / 2 - (y - rect.top)) / (canvas.height / 2)
複製程式碼
以x座標為例,我們簡單的說明下。
ev.clientX
為滑鼠點選時滑鼠距離瀏覽器左邊的距離。
rect.left
為canvas元素左邊距離瀏覽器螢幕左邊的距離。
那麼x-rect.left
就是滑鼠距離瀏覽器左邊的距離減去canvas元素距離瀏覽器左邊的距離,得到的就是滑鼠距離canvas左邊的距離。
x - rect.left - canvas.width / 2
這個式子計算的結果的絕對值就是滑鼠距離canvas中心點(webgl原點)的距離。
(x - rect.left - canvas.width / 2) / (canvas.width / 2)
就是滑鼠點選時滑鼠在wegl系統中的座標值。
y座標同理,因為我們就得到了滑鼠點選時的x、y座標。
6. 改變點的顏色
要改變點的顏色,就要使用片元著色器。可以使用uniform變數,將顏色值傳給著色器。程式碼如下:
html程式碼同上,我這裡就不寫了。
js程式碼:
// 頂點著色器 注意這裡的\n不能省略。否則不符合語法
var VSHADER_SOURCE = `
attribute vec4 a_Position\n;
void main(){\n
gl_Position = a_Position;\n
gl_PointSize = 10.0;\n
}\n
`
// 片段(片元)著色器
var FSHADER_SOURCE = `
precision mediump float;\n
uniform vec4 u_FragColor;\n
void main(){\n
gl_FragColor = u_FragColor;\n
}\n
`
function main () {
// 獲取canvas
var canvas = document.getElementById('canvas')
// 獲取webgl繪圖上下文
var gl = canvas.getContext('webgl')
if (!gl) {
console.log('Failed to get the rendering context')
retun
}
// 初始化著色器函式
if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log('設定著色器失敗')
return
}
// 獲取attribute的儲存位置
var a_Position = gl.getAttribLocation(gl.program, 'a_Position')
if (a_Position < 0) {
console.log('Failed to get the storage of a_Position')
return
}
// 獲取u_FragColor變數的儲存位置
var u_FragColor = gl.getUniformLocation(gl.program, 'u_FragColor')
// 註冊滑鼠點選事件響應函式
canvas.onmousedown = function (ev) {
click(ev, gl, canvas, a_Position)
}
// 指定清空canvas的顏色
gl.clearColor(0.0, 0.0, 0.0, 1.0)
// 清空canvas
gl.clear(gl.COLOR_BUFFER_BIT)
// 滑鼠的點選位置陣列
var g_pointArr = []
// 儲存點顏色陣列
var g_colorArr = []
// 滑鼠點選處理函式
function click (ev, gl, canvas, a_Position) {
var x = ev.clientX
var y = ev.clientY
var rect = ev.target.getBoundingClientRect()
// 計算點選位置在webgl系統中的x座標(原書中這裡有錯誤)
var x = (x - rect.left - canvas.width / 2) / (canvas.width / 2)
// 計算點選位置在webgl系統中的y座標(原書中有錯誤)
var y = (canvas.height / 2 - (y - rect.top)) / (canvas.height / 2)
// 將點x和y的座標存入陣列
g_pointArr.push([x, y])
// 將點的顏色儲存到g_colorArr中
if (x >= 0.0 && y >= 0.0) {
// 第一象限 紅色
g_colorArr.push([1.0, 0.0, 0.0, 1.0])
} else if (x < 0.0 && y < 0.0) {
// 第三象限 綠色
g_colorArr.push([0.0, 1.0, 0.0, 1.0])
} else {
// 其他象限 白色
g_colorArr.push([1.0, 1.0, 1.0, 1.0])
}
// 清空canvas
gl.clear(gl.COLOR_BUFFER_BIT)
var len = g_pointArr.length
for (var i = 0; i < len; i++) {
gl.vertexAttrib3f(a_Position, g_pointArr[i][0], g_pointArr[i][1], 0.0)
console.log('u_FragColor:', u_FragColor)
// 將點的顏色傳給u_FragColor變數中
gl.uniform4f(
u_FragColor,
g_colorArr[i][0],
g_colorArr[i][1],
g_colorArr[i][2],
g_colorArr[i][3]
)
gl.drawArrays(gl.POINTS, 0, 1)
}
}
}
window.onload = function () {
main()
}
複製程式碼
7. 程式解析
7.1 宣告uniform變數
使用片元著色器就必須使用uniform變數,當然你也可以使用varying變數,不過varying變數要複雜的多,我們暫時先不講它。
使用uniform變數之前,首先需要按照與宣告attribute變數相同的格式<儲存限定符><型別><變數名>來宣告uniform變數。
uniform vec4 u_FragColor
複製程式碼
在宣告uniform變數之前,我們還使用精度限定詞指定了變數的範圍和精度,本例中為中等精度。關於精度的詳細討論我們在第5章再講。
precision mediump float
複製程式碼
7.2 獲取uniform變數的儲存地址
gl.getUniformLocation(program,name)
型別 | 引數 | 含義 |
---|---|---|
引數 | program | 指定包含頂點著色器和片元著色器的程式物件 |
name | 指定想要獲取儲存地址的uniform變數名稱 | |
返回值 | non-null | 指定的uniform變數位置 |
null | 指定的uniform變數不存在,或者其命名具有gl_或者webgl_字首 | |
錯誤 | INVALID_OPPERATION | 程式物件未能成功連線 |
INVALID_VALUE | name引數長度大於uniform變數名的最大長度(預設256位元組) |
7.3 向uniform變數賦值
gl.uniform4f(location,v0,v1,v2,v3)
型別 | 引數 | 含義 |
---|---|---|
引數 | location | 將要修改的uniform變數的儲存位置 |
v0,v1,v2,v3 | 指定填充uniform變數的第1-4分量 | |
返回值 | 無 | |
錯誤 | INVALID_OPERTAION | 沒有當前program物件,或者location是非法的變數儲存位置 |
7.4 gl.uniform4f()
同族函式
gl.uniform4f
也有一系列的同族函式,gl.uniform1f()
函式傳遞1個值,gl.uniform2f()
函式傳遞2個值,gl.uniform3f()
函式傳遞3個值,
總結
在這一章中我們學習了,一些webgl的核心函式,並學習瞭如何使用它們,我們重點學習了著色器的相關知識,它是webgl的基石。