WebGL 初探

該文章於一天前發表在 github,如有問題可提至 githubgit

目前,咱們有不少方案能夠快速的接觸到 WebGL 並繪製複雜的圖形,但最後發現咱們忽視了不少細節性的東西。固然,這對初學 WebGL 是有必要的,它能迅速提起咱們對 WebGL 的學習興趣。當學習到更加深刻的階段時,咱們更想了解 WebGL 的工做機制,這也將對咱們編程有極大的幫助。以上也是我想寫這樣一個系列的緣由。github

簡介

用更專業的描述講,WebGL (Web Graphics Library) 是一個用以渲染交互式 3D 和 2D 圖形的無需插件且兼容下一代瀏覽器的 JavaScript API,經過 HTML5 中 <canvas> 元素實現功能。WebGL 是由 Khronos Group 集團制定,而非 W3C 組織。目前,咱們可使用的是 WebGL 第一個版本,它繼承自 OpenGL ES 2.0 。而 OpenGL ES (OpenGL for Embedded Systems) 是 OpenGL 三維圖形 API 的子集,針對手機、PDA 和遊戲主機等嵌入式設備而設計。如下是各版本之間的關係圖:web

版本關係

Hello World

首先,咱們將經過實現一個簡單的 WebGL 程序(清空繪圖區)叩開 WebGL 的大門。下面將實現一個最簡單的 WebGL 功能:編程

處理流程

建立 canvas 元素

WebGL 採用 HTML5 中的 <canvas> 元素。爲了使用 WebGL 進行 3D 渲染,你首先須要一個 canvas 元素。這裏建立了一個 canvas 元素,並使用 onload 事件建立來初始化 WebGL 上下文。canvas

<body onload="start()">
  <canvas id="glcanvas" width="640" height="480">
    Your browser doesn't appear to support the HTML5 <code>&lt;canvas&gt;</code> element.
  </canvas>
</body>

獲取 WebGL 上下文

目前,各瀏覽器基本都實現了對 WebGL 的支持,但 IE11 及 Edge 瀏覽器稍微有些不一樣。如下是對初始化 WebGL 的基本封裝:瀏覽器

function initWebGL(canvas) {
  // 建立全局變量
  window.gl = null;
  
  try {
    // 嘗試獲取標準上下文,若是失敗,回退到試驗性上下文
    gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
  }
  catch(e) {
    throw '建立失敗。';
  }
  
  // 若是沒有GL上下文,立刻放棄
  if (!gl) {
    alert("WebGL初始化失敗,多是由於您的瀏覽器不支持。");
    gl = null;
  }
  return gl;
}

這裏經過採用 canvas 的 getContext(contextType, contextAttributes) 方法判斷瀏覽器是否支持 WebGL,並建立其上下文。當返回值是 canvas 的上下文時,瀏覽器可支持 WebGL,爲 null 時,則建立失敗。注意,在 IE11 及 Edge 瀏覽器下,須要使用 "experimental-webgl" 建立 WebGL,此處作了兼容處理。緩存

清空繪圖區

下面將背景顏色設置爲黑色,並清空緩存區。app

var gl; // WebGL的全局變量

function start() {
  var canvas = document.getElementById("glcanvas");

  // 初始化 WebGL 上下文
  gl = initWebGL(canvas);   
  
  // 只有在 WebGL 可用的時候才繼續
  
  if (gl) {
    // 設置清除顏色爲黑色,不透明
    gl.clearColor(0.0, 0.0, 0.0, 1.0);     
    // 清除顏色和深度緩存
    gl.clear(gl.COLOR_BUFFER_BIT|gl.DEPTH_BUFFER_BIT);     
  }
}

這樣,咱們能夠在瀏覽器中看到一塊黑色區域。你可能已經注意到,WebGL 遵循的是傳統 OpenGL 顏色份量的取值範圍,從 0.0 到 1.0。RGB 的值越高,顏色越亮。注意,clear() 方法在這裏清除顏色和深度緩存,而不是繪製區域的 <canvas>,該方法繼承自 OpenGL(基於多緩存模型)。實際還有模版緩存,但實際不多會被用到。編程語言

更進一步

上面咱們完成了第一個 WebGL 程序,可是咱們還未接觸到 WebGL 的核心:可編程着色器。接下來,咱們將使用可編程着色器在屏幕上繪製點。可編程着色器是一個較爲複雜的概念,也有本身的編程語言 GLSL,後面將會又專門的文章具體講解可編程着色器。這裏咱們只須要簡單瞭解繪製的流程:函數

處理流程

編寫着色器程序

WebGL 是沒法像 OpenGL 利用固定渲染管線,代替它的是可編輯渲染管線中的 GLSL 着色語言。下面是頂點及片元着色器 GLSL 程序,用字符串表示,它將直接運行在瀏覽器之上。

// 頂點着色器程序
var VSHADER_SOURCE = 
  'void main() {\n' +
  '  gl_Position = vec4(0.0, 0.0, 0.0, 1.0);\n' + // 設置頂點位置
  '  gl_PointSize = 10.0;\n' +                    // 設置點的大小
  '}\n';

// 片元着色器程序
var FSHADER_SOURCE =
  'void main() {\n' +
  '  gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);\n' + // 設置點的顏色,此處爲白色
  '}\n';

上面程序是否是有中似曾相識的感受?沒錯,GLSL 語言和 C 語言很相似。着色器程序中包含一個主函數,且返回值爲空。其中 vec4() 構造函數用於生成一個四維向量(x,y,z,w)。

編譯着色器

首先,須要用 createShader( type ) 方法生成相應類型的 WebGLShader。接着,使用 shaderSource( shader, sourceCode ) 做爲 GLSL 源碼的鉤子函數。最後使用 compileShader( shader ) 完成對着色器的編譯。程序中咱們作了編譯後的校驗,當着色器編譯失敗時,會報出失敗並刪除着色器。

function createShader (gl, type, sourceCode) {
  // 編譯着色器類型:頂點着色器及片元着色器。
  var shader = gl.createShader( type );
  gl.shaderSource( shader, sourceCode );
  gl.compileShader( shader );

  if ( !gl.getShaderParameter(shader, gl.COMPILE_STATUS) ) {
    var info = gl.getShaderInfoLog( shader );
    console.log( "沒法編譯 WebGL 程序。 \n\n" + info);
    gl.deleteShader(shader);
    return null;
  }

  return shader;
}

鏈接到可用程序

此時,着色器還是不可用的,須要將其賦值到 WebGLProgram 上。這裏主要進行了三步操做,首先,須要使用 createProgram() 方法建立和初始化一個 WebGLProgram 對象。接着,使用 gl.attachShader(program, shader) 將該對象結合兩個已經編譯的着色器。最後,使用 linkProgram(program) 將 WebGLProgram 和着色器鏈接。

function createProgram(gl, vshader, fshader) {
  // 建立着色器對象
  var vertexShader = createShader(gl, gl.VERTEX_SHADER, vshader);
  var fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fshader);
  if (!vertexShader || !fragmentShader) {
    return null;
  }

  // 建立編程對象
  var program = gl.createProgram();
  if (!program) {
    return null;
  }

  // 賦值已建立的着色器對象
  gl.attachShader(program, vertexShader);
  gl.attachShader(program, fragmentShader);

  // 鏈接編程對象
  gl.linkProgram(program);

  // 檢查連接結果
  var linked = gl.getProgramParameter(program, gl.LINK_STATUS);
  if (!linked) {
    var error = gl.getProgramInfoLog(program);
    console.log('連接程序失敗:' + error);
    gl.deleteProgram(program);
    gl.deleteShader(fragmentShader);
    gl.deleteShader(vertexShader);
    return null;
  }
  return program;
}

使用可用着色器程序

這一步主要使用 useProgram(program) 方法告訴 GPU 使用程序。

function initShaders(gl, vshader, fshader) {
  var program = createProgram(gl, vshader, fshader);
  if (!program) {
    console.log('建立程序失敗。');
    return false;
  }

  gl.useProgram(program);
  gl.program = program;

  return true;
}

繪製一個點

最後,使用 drawArrays(mode, first, count) 繪製一個點,該函數是一個很是強大的渲染函數,後續文章會有詳細介紹。此處只須要知道傳入 "POINTS" 繪製了一個點。

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

至此,咱們已經完成了繪製一個點的所有程序。當運行以上程序時,咱們會在瀏覽器中看到一個白色的點。

結束語

到如今,咱們雖然尚未使用 WebGL 繪製三維圖形,但咱們已經進入了 WebGL 世界。咱們已經使用 WebGL 繪製了簡單的圖形。可是這只是 WebGL 的繪製的冰山一角,咱們使用 WebGL 固然不是爲了繪製這樣一個簡單的圖形。爲了繪製更復雜的圖形,咱們還有不少的細節須要去了解。可是不管如何,咱們都已經開啓了 WebGL 的第一步,其實問題也並無咱們想象的那麼難。

相關文章
相關標籤/搜索