webgl入門(3)-座標系與滑鼠互動

郭邯發表於2018-12-25

前言

這是webgl入門的第2節,內容包含weblg的座標系和動態設定圖形樣式。

1. webgl座標系統

由於webgl處理的是三維圖形,所以它使用三維座標系統。具有x軸、y軸、z軸。當你面對螢幕的時候,X軸是水平的(正方形向右),Y軸是垂直的(正方向向上(原文中有錯)),而Z軸垂直於螢幕(正方向向外) 這套座標系又叫右手座標系。

webgl座標系

如圖所示,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)

原書關於這裡的描述是錯誤的,已訂正

webgl座標與canvas座標

你可以點選這裡通過修改gl_Position來檢視效果。

2.繪製一個點(版本2)

這一節我們將討論如何在Javascript和著色器之間傳輸資料。

在這一節中,webgl程式可以將頂點位置座標從js傳到著色器程式中我,然後在對應的位置將點繪製出來。

2.1 attribute變數

我們的目標是將位置資訊從js傳到著色器,有2種方式可以做到這點:attribute變數和uniform變數。使用哪個變數取決於要傳遞的資料本身。

  • attribute變數傳輸的是與頂點有關的資料
  • uniform變數傳輸的是那些對於所有頂點都相同的資料

attribute變數是GLSL ES變數,被用來從外部向頂點著色器內部傳遞資料,只有頂點著色器能使用它

使用attribute變數,程式應該包含如下步驟:

  1. 在頂點著色器中,宣告attribute變數
  2. 將attribute變數賦值給gl_Position變數
  3. 向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的基石。

相關文章