WebGL簡易教程(一):第一個簡單示例

charlee44發表於2019-08-04

1. 概述

不得不說現在三維圖形渲染技術更新換代實在是太快,OpenGL很多資料還沒來得及學習就已經有點落伍了。NeHe的學習教程還有之前用的《OpenGL程式設計指南》第七版(也就是紅寶書)都非常好,可惜它們都是從固定管線開始講起的;而現在可程式設計管線的技術已經是非常常見的基礎技術了。後來我還看過《OpenGL程式設計指南》第八版(白皮書),這本教程是從可程式設計管線(著色器)開始講起的,看的時候就覺得沒有前面的基礎打底,顯得非常的晦澀,遠不如紅寶書易懂。羞愧的說,我已經多次入門失敗了。

這也正是我寫這篇教程的原因,希望從繁雜的資料中總結真正有用的知識(當然也希望能幫助到你)。我覺得WebGL是學習OpenGL系列三維圖形渲染技術很好的入門點。WebGL是OpenGL的瀏覽器版本,基本上可以認為是OpenGL的子集,能被WebGL保留而不剔除的技術,必須是三維圖形渲染技術的精華。在這裡給大家強烈推薦《WebGL程式設計指南》這本書,我這篇教程正是在這本書的基礎之上總結出來的。

在學習OpenGL/WebGL的時候,我還感覺到很多資料舉得例子往往都太簡單了,確實是一看就懂,但是在實際遇到的問題的時候卻往往解決不了。我還是認為在實際中解決問題,更能加深對知識的理解。正好最近我在研究GIS中地形的繪製,那麼我就通過一步一步繪製地形的示例,來總結WebGL的相關知識。如果你不懂GIS這些術語也不要緊,只需要知道我這裡的最終目的是想繪製的是一個大地高程模型,是一個包含XYZ座標的點集,表達了地形的情況。

2. 示例:繪製一個點

編寫WebGL程式跟編寫Web前端程式的步驟是一樣的,包含HTML和JavaScript兩個部分,通過瀏覽器進行除錯。

1) HelloPoint1.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Draw a point (1)</title>
  </head>

  <body onload="main()">
    <canvas id="webgl" width="400" height="400">
    Please use a browser that supports "canvas"
    </canvas>

    <script src="../lib/webgl-utils.js"></script>
    <script src="../lib/webgl-debug.js"></script>
    <script src="../lib/cuon-utils.js"></script>
    <script src="HelloPoint1.js"></script>
  </body>
</html>

這一段HTML非常簡單,從實際表現上來說就是建立了一個畫布<canvas>。<canvas>是HTML5引入的的一個繪製標籤,可以在畫布中繪製任意圖形。WebGL正是通過<canvas>元素進行繪製的。
除此之外,這段程式碼還通過<script>標籤引入了幾個外部JS檔案。其中lib目錄中的幾個JS檔案是一些通用的元件(來自《WebGL程式設計指南》的原始碼),可以先暫時不用關心其具體實現;最後一個匯入的HelloPoint1.js正是我們編寫的繪製模組。而在<body>標籤中定義的onload事件屬性繫結的正是HelloPoint1.js中的main()函式。

2) HelloPoint1.js

// 頂點著色器程式
var VSHADER_SOURCE = 
  'void main() {\n' +
  '  gl_Position = vec4(0.0, 0.0, 0.0, 1.0);\n' + // Set the vertex coordinates of the point
  '  gl_PointSize = 10.0;\n' +                    // Set the point size
  '}\n';

// 片元著色器程式
var FSHADER_SOURCE =
  'void main() {\n' +
  '  gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n' + // Set the point color
  '}\n';

function main() {
  // 獲取 <canvas> 元素
  var canvas = document.getElementById('webgl');

  // 獲取WebGL渲染上下文
  var gl = getWebGLContext(canvas);
  if (!gl) {
    console.log('Failed to get the rendering context for WebGL');
    return;
  }

  // 初始化著色器
  if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
    console.log('Failed to intialize shaders.');
    return;
  }

  // 指定清空<canvas>的顏色
  gl.clearColor(0.0, 0.0, 0.0, 1.0);

  // 清空<canvas>
  gl.clear(gl.COLOR_BUFFER_BIT);

  // 繪製一個點
  gl.drawArrays(gl.POINTS, 0, 1);
}

這段JS程式碼的主要內容就是前面提到的main函式,一旦HTML被瀏覽器載入成功,這段指令碼就會執行。在main函式中主要有一下幾步:

(1) 準備工作

document.getElementById('webgl'):文件物件模型DOM的函式,獲取到HTML頁面的<canvas>元素。

getWebGLContext(canvas):獲取WebGL渲染上下文,儲存在gl變數中。因為不同瀏覽器獲取函式不太一樣,所以通過元件cuon-utils提供的函式來統一行為。

(2) 著色器

initShaders:初始化著色器。

首先要知道什麼是著色器。如果你只學習過固定管線或者其他的二維繪圖元件(如GDI),就會非常困惑著色器是什麼,為什麼要用著色器。比如說在固定管線中,繪製點就是drawPoint,繪製線就drawLine。而在WebGL中,繪製工作則主要被分解成頂點著色器和片元著色器兩個步驟了。

在啟動JS程式後,繪製工作首先進入的是頂點著色器,在頂點著色器中描述頂點特性(如位置、顏色等),頂點就是三維空間的點,比如三角形的三個頂點;然後進入到片元著色器,在片元著色器中逐片元處理畫素(如光照、陰影、遮擋)。最後片元傳入到顏色緩衝區,進行顯示。渲染過程如下:

WebGL渲染管線

這個過程是一個類似水流的流向過程,所以這個過程被稱為渲染管線(Pipeline)。並且,這個過程是需要我們去程式設計控制的,比如觀察者的視角變化需要在頂點著色器去調控;光線對顏色的變化需要在片元著色器去調控等;因此,這個過程就是可程式設計管線。通過著色器程式,三維影象渲染就更加的靈活強大。

在initShaders()函式中,傳入了預先定義的JS字串VSHADER_SOURCE和FSHADER_SOURCE。需要說明是,著色器程式是以字串的形式嵌入到JS檔案中執行的。這個函式同樣是cuon-utils元件提供的,呼叫之後就告訴WebGL系統著色器已經建立好了並可以隨時使用。

(3) 頂點著色器

頂點著色器的定義如下:

// 頂點著色器程式
var VSHADER_SOURCE = 
  'void main() {\n' +
  '  gl_Position = vec4(0.0, 0.0, 0.0, 1.0);\n' + // Set the vertex coordinates of the point
  '  gl_PointSize = 10.0;\n' +                    // Set the point size
  '}\n';

前面說到頂點著色器程式是嵌入在JS中的程式,所以雖然傳入的是字串,但其實本質是著色器描述語言(GLSL:OpenGL Shading Language)。既然是語言也就有自己的函式與變數定義。main()函式是每個著色器程式定義的入口。在main函式中,將頂點的座標賦值給內建變數gl_Position,點的尺寸賦值給內建變數gl_PointSize。

注意這裡的gl_Position是必須賦值的,否則著色器不會正常工作。賦值的型別是vec4,也就是一個四維向量。一般來說,描述點位只需要三維向量就可以了,但是很多情況下需要四個分量的齊次座標。齊次座標(x,y,z,w)等價於三維座標(x/w,y/w,z/w)。所以如果第四個分量是1,那麼就是普通的三維座標;如果第四分量為0,就表示無窮遠的點。

(4) 片元著色器

片元著色器的定義如下:

// 片元著色器程式
var FSHADER_SOURCE =
  'void main() {\n' +
  '  gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n' + // Set the point color
  '}\n';

如同頂點著色器一樣,片元著色器將點的顏色賦值給gl_FragColor變數,gl_FragColor是片元著色器唯一的內建變數,控制畫素在螢幕上的最終顏色。

(5) 清空緩衝區

gl.clearColor():設定清空的背景色。
gl.clear(gl.COLOR_BUFFER_BIT): 清空顏色緩衝區。

(6) 繪製操作

gl.drawArrays(gl.POINTS, 0, 1):繪製一個點。
頂點著色器只是指定了繪製的頂點,還需要指定頂點到底成點、成線還是成面,gl.drawArrays()就是這樣一個函式,這裡告訴WebGL系統應該繪製一個點。

3. 結果

最終的執行結果很簡單,在Chrome開啟HelloPoint1.html,頁面顯示了一個繪製一個點的視窗:
WebGL示例顯示結果

4. 參考

本來部分程式碼和插圖來自《WebGL程式設計指南》,原始碼連結:https://share.weiyun.com/5VjlUKo ,密碼:sw0x2x。會在此共享目錄中持續更新後續的內容。

相關文章