突襲HTML5之WebGL 3D概述

WebGL開啓了網頁3D渲染的新時代,它容許在canvas中直接渲染3D的內容,而不借助任何插件。WebGL同canvas 2D的API同樣,都是經過腳本操縱對象,因此步驟也是基本類似:準備工做上下文,準備數據,在canvas中繪製對象並渲染。與2D不一樣的就是3D涉及的知識更多了,例如世界、光線、紋理、相機、矩陣等專業知識。WebGL有一個很好的中文教程,就是下面使用參考中的第一個連接,因此這裏再也不班門弄斧,後面的內容只是簡單的總結一下學習的內容。html

瀏覽器的支持node

因爲微軟有本身的圖形發展計劃,一直不支持WebGL,因此IE目前除了安裝插件外,是沒法運行WebGL的。其餘的主流瀏覽器如Chrome、FireFox、Safari、Opera等,都裝上最新的版本就能夠了。除了瀏覽器要裝最新的外,還要保證顯卡的驅動也是最新的。
裝上這些之後,能夠打開瀏覽器,輸入下面的網址驗證一下瀏覽器對WebGL的支持狀況:http://webglreport.sourceforge.net/。web

在正常安裝以上瀏覽器以後仍是不能運行WebGL,那你能夠強制開啓WebGL支持試一試。開啓方法以下:chrome

Chrome瀏覽器編程

咱們須要爲Chrome加入一些啓動參數,如下具體操做步驟以Windows操做系統爲例:找到Chrome瀏覽器的快捷方式,右鍵點擊快捷方式,選擇屬性;在目標框內,chrome.exe後面的引號後面,加入如下內容:canvas

--enable-webgl --ignore-gpu-blacklist --allow-file-access-from-files瀏覽器

點擊肯定後關閉Chrome,而後用此快捷方式啓動Chrome瀏覽器。網絡

幾個參數的含義以下:app

--enable-webgl的意思是開啓WebGL支持;框架

--ignore-gpu-blacklist的意思是忽略GPU黑名單,也就是說有一些顯卡GPU由於過於陳舊等緣由,不建議運行WebGL,這個參數可讓瀏覽器忽略這個黑名單,強制運行WebGL;

--allow-file-access-from-files的意思是容許從本地載入資源,若是你不是WebGL的開發者,不須要開發調試WebGL,只是想要看一下WebGL的Demo,那你能夠不添加這個參數。

Firefox瀏覽器

Firefox的用戶請在瀏覽器的地址欄輸入「about:config」,回車,而後在過濾器(filter)中搜索「webgl」,將webgl.force-enabled設置爲true;將webgl.disabled設置爲false;在過濾器(filter)中搜索「security.fileuri.strict_origin_policy」,將security.fileuri.strict_origin_policy設置爲false;而後關閉目前開啓的全部Firefox窗口,從新啓動Firefox。

前兩個設置是強制開啓WebGL支持,最後一個security.fileuri.strict_origin_policy的設置是容許從本地載入資源,若是你不是WebGL的開發者,不須要開發調試WebGL,只是想要看一下WebGL的Demo,那你能夠不設置此項。

Safari瀏覽器

在菜單中找到「屬性」→「高級」,選中「顯示開發菜單」,而後到「開發」菜單,選中「開啓WebGL」。

開發步驟

下面的代碼只是簡單總結一下相關的概念,它來源於參考中的中文教程,涉及較多的3D方面的知識。感興趣的同窗直接能夠跳到實用參考中的中文教程中學習,比我這裏講解的要詳細和準確的多。湊熱鬧的同窗簡單看看就能夠了,不用深究每一行代碼的含義。

準備工做

這個不用說了,就是在頁面上添加一個canvas元素做爲渲染的容器。例如:

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

下面就是正式開始寫腳本的時候了,首先看一下程序入口以及總體結構:

 
function start() {
    var canvas = document.getElementById("glcanvas");  
    initGL(canvas);        
    initShaders();        
    initBuffers(); 
       
    gl.clearColor(0.0, 0.0, 0.0, 1.0);        
    gl.enable(gl.DEPTH_TEST); 
       
    drawScene();    
} 

這裏的幾個方法表明了典型的WebGL的繪製步驟:

步驟一:初始化WebGL工做環境 - initGL

這個方法的代碼以下:

 
var gl;    
function initGL(canvas) {  
  gl=null;      
  try {
    // Try to grab the standard context. If it fails, fallback to experimental.
    gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
  }
  catch(e) {}

  // If we don't have a GL context, give up now
  if (!gl) {
    alert("Unable to initialize WebGL. Your browser may not support it.");
  }
} 

這個方法很簡單,就是獲取WebGL的繪製環境,須要把參數"webgl"傳給canvas.getContext方法就好了,可是因爲目前WebGL的標準沒有最終定型,因此實驗階段用的參數都是"experimental-webgl"。固然你直接去調用canvas.getContext("experimental-webgl")也是能夠的,等標準定下之後,你再修改一個代碼。

步驟二:初始化着色器Shaders - initShaders

着色器Shader概念比較簡單,說白了就是顯卡運算指令。構造3D場景須要進行大量的顏色、位置等等信息的計算,若是這些計算由軟件執行的話,速度會很慢。因此把這些運算讓顯卡去計算,速度就很快;如何去執行這些計算,就是由着色器指定的。着色器代碼是用一種叫作GLSL的着色器語言編寫的,這個咱們不去講述這個語言了。

着色器能夠在html中定義,在代碼中使用。固然了你在程序中用一個字符串去定義着色器也是同樣的。

下面先看定義的部分:

 
<script id="shader-fs" type="x-shader/x-fragment">
    precision mediump float;
    varying vec4 vColor;

    void main(void) {
        gl_FragColor = vColor;
    }
script>
<script id="shader-vs" type="x-shader/x-vertex">
    attribute vec3 aVertexPosition;
    attribute vec4 aVertexColor;

    uniform mat4 uMVMatrix;
    uniform mat4 uPMatrix;

    varying vec4 vColor;

    void main(void) {
        gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
        vColor = aVertexColor;
    }
</script>

這裏有兩個着色器:面着色器和頂點着色器。

關於這兩個着色器,這裏有必要說明一下,計算機中的3D模型基本都是由點結合三角面片去描述的,頂點着色器就是去處理這些點的數據,而面着色器就是經過插值的方式,去處理三角面片上點的數據。

上面定義的頂點着色器就定義了頂點的位置和顏色計算方式;而面着色器定義了插值點的顏色計算方式。實際的應用場景中,還會涉及到在着色器中處理光線等效果。

定義了着色器,在程序中就能夠查找到它們並能夠去使用:

 
    var shaderProgram;

    function initShaders() {
        var fragmentShader = getShader(gl, "shader-fs");
        var vertexShader = getShader(gl, "shader-vs");

        shaderProgram = gl.createProgram();
        gl.attachShader(shaderProgram, vertexShader);
        gl.attachShader(shaderProgram, fragmentShader);
        gl.linkProgram(shaderProgram);

        if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
            alert("Could not initialise shaders");
        }

        gl.useProgram(shaderProgram);

        shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
        gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);

        shaderProgram.vertexColorAttribute = gl.getAttribLocation(shaderProgram, "aVertexColor");
        gl.enableVertexAttribArray(shaderProgram.vertexColorAttribute);

        shaderProgram.pMatrixUniform = gl.getUniformLocation(shaderProgram, "uPMatrix");
        shaderProgram.mvMatrixUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix");
    }

着色器是有了,可是怎麼讓顯卡去執行,Program就是這種橋樑,它是WebGL原生的二進制碼,它的做用基本上就是讓顯卡運行着色器代碼去渲染指定的模型數據。
這裏還用到一個輔助方法getShader,這個方法就是遍歷html文檔,查找着色器的定義,拿到定義後建立着色器,這裏就不細說了:

 
function getShader(gl, id) {
    var shaderScript, theSource, currentChild, shader;
    
    shaderScript = document.getElementById(id);    
    if (!shaderScript) {
        return null;
    }
    
    theSource = "";
    currentChild = shaderScript.firstChild;    
    while(currentChild) {
        if (currentChild.nodeType == currentChild.TEXT_NODE) {
            theSource += currentChild.textContent;
        }
        
        currentChild = currentChild.nextSibling;
    }
    if (shaderScript.type == "x-shader/x-fragment") {
      shader = gl.createShader(gl.FRAGMENT_SHADER);
    } else if (shaderScript.type == "x-shader/x-vertex") {
      shader = gl.createShader(gl.VERTEX_SHADER);
    } else {
     // Unknown shader type
     return null;
    }
   gl.shaderSource(shader, theSource);
    
   // Compile the shader program
   gl.compileShader(shader);  
    
   // See if it compiled successfully
   if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {  
      alert("An error occurred compiling the shaders: " + gl.getShaderInfoLog(shader));  
      return null;  
   }
    
   return shader;
} 

步驟三:建立/加載模型數據 - initBuffers

這些小例子中,模型數據基本都是直接生成的,實際的程序中,這些數據應該都是從模型加載獲得的:

 
    var triangleVertexPositionBuffer;
    var triangleVertexColorBuffer;

    function initBuffers() {
        triangleVertexPositionBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer);
        var vertices = [
             0.0,  1.0,  0.0,
            -1.0, -1.0,  0.0,
             1.0, -1.0,  0.0
        ];
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
        triangleVertexPositionBuffer.itemSize = 3;
        triangleVertexPositionBuffer.numItems = 3;

        triangleVertexColorBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexColorBuffer);
        var colors = [
            1.0, 0.0, 0.0, 1.0,
            0.0, 1.0, 0.0, 1.0,
            0.0, 0.0, 1.0, 1.0
        ];
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);
        triangleVertexColorBuffer.itemSize = 4;
        triangleVertexColorBuffer.numItems = 3;
    } 

上面這段代碼建立了三角形的頂點和頂點的顏色數據並放在緩衝區中。

步驟四:渲染 - drawScene

準備好了數據之後,交給WebGL去渲染就行了,這裏調用的是gl.drawArrays方法。看代碼:

 
    function drawScene() {
        gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

        pMatrix = okMat4Proj(45.0, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0);
        mvMatrix = okMat4Trans(-1.5, 0.0, -7.0);

        gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer);
        gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, triangleVertexPositionBuffer.itemSize,
  gl.FLOAT, false, 0, 0);

        gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexColorBuffer);
        gl.vertexAttribPointer(shaderProgram.vertexColorAttribute, triangleVertexColorBuffer.itemSize, 
gl.FLOAT, false, 0, 0);

        setMatrixUniforms();
        gl.drawArrays(gl.TRIANGLES, 0, triangleVertexPositionBuffer.numItems);
    } 

這個函數首先設置了3D世界的背景爲黑色,而後設置投影矩陣,設置待繪製對象的位置,而後根據緩衝中的頂點和顏色數據,繪製對象。這裏還有一些生成投影矩陣和模型視圖矩形的輔助方法(使用了Oak3D圖形庫中的矩陣輔助方法)與主題關係不大,這裏就不詳細解釋了。

基本上流程就是這麼多了,更復雜的紋理,光線等都是在這些基礎上加入一些WegGL的特性實現的,這個請參看後面的中文教程,裏面有詳細的例子。

怎麼樣?使用原生的WebGL開發是一種什麼感覺?不只須要有深厚的3D知識,還須要知道各類實現細節。WebGL這樣作是爲了靈活的適應各類應用場景,可是對於大多數像我這樣非專業人士來講,不少細節是不須要知道的。這樣就催生了各類輔助開發的類庫,例如這節用到的Oak3D庫(爲了演示WebGL開發,例子中只用到了矩陣輔助方法)。下一節會介紹一個用的比較多的Three.js圖形庫。

前面咱們看到了使用原生的WebGL API開發是多麼的累,正由於如此,大量的WebGL框架被開發出來。使用這些框架,你能夠快速建立須要的3D場景。這些框架不一樣程度的封裝了建立3D場景的各類要素,例如場景,相機、模型、光照、材質等等;使用這些封裝起來的對象,就能夠很簡單的建立須要的3D場景,這樣你就只須要把更多精力放在邏輯方面就能夠了。

目前並無哪個具備能壓倒其餘框架的優點,選擇什麼樣的框,仍是看我的喜愛吧,不過選擇框架的時候,我的以爲仍是多看看框架最後的更新時間,選擇穩定更新的框架能讓你始終能使用上最新的特性,使你的程序穩定性更好。

下面的例子就使用了Three.js框架進行開發。

Three.js是一個比較全面的開源框架,它良好的封裝的3D場景的各類要素。你能夠用它來很容易的去建立攝像機,模型,光照,材質等等。你還能夠選擇不一樣的渲染器,Three.js提供了多種渲染方式,你能夠選擇使用canvas來渲染,也可使用WebGL或者SVG來進行渲染。

此外,Three.js能夠加載不少格式的3D文件,你的模型文件能夠來自Blender,Maya,Chinema4D,3DMax等等。並且內置了比較基礎的東西:(球體)Spheres, (飛機)Planes, (立方體) Cubes, (圓柱體)Cylinders。Three.js建立這些物體會很是的容易。

好了,不廢話了,直接看代碼:

 
<DOCTYPE html>
<html>
 <head>
  <title>threeJSDemo title>
  <meta charset="utf-8">
  <style>
   body
   {
    margin:0px;
    background-color:#B0B0B0;
    overload:hidden;
   }
  </style>
 </head>
 <body>
  <script src="Three.js">script>
  <script>
   var camera,scene,renderer;
   var mesh;
   init();
   animate();
   
   function init(){
    scene = new THREE.Scene();
    camera = new THREE.PerspectiveCamera(70,window.innerWidth / window.innerHeight,1,1000);
    camera.position.z = 400;
    scene.add(camera);   
    geometry = new THREE.CubeGeometry(200,200,200);
    material = new THREE.MeshBasicMaterial( { color: 0xff0000, wireframe: true } );
    mesh = new THREE.Mesh(geometry,material);
    scene.add(mesh);
    renderer = new THREE.WebGLRenderer();
    renderer.setSize(window.innerWidth,window.innerHeight);
    document.body.appendChild(renderer.domElement);
   }  

   function animate() {
    requestAnimationFrame( animate );
    mesh.rotation.x += 0.05;
    mesh.rotation.y += 0.05;
    renderer.render( scene, camera );
   }
   </script>   
 </body>
</html> 

這個是所有的代碼,相對於前面使用WebGL的API的代碼,這個簡直就是太簡單了。

代碼很直觀,就那麼幾步:

1. 建立場景scene。

2. 建立攝像機camera。

3. 建立/加載模型geometry。

4. 加載材質material。

5. 渲染模型對象mesh(是由geometry和material組成)。

6. 啓用動畫。

這是每一個框架都提供的功能,使用不一樣的框架除了函數的名稱可能不一樣之外,這些步驟基本都是同樣的。下面的參考中列出了不少的框架學習文檔,你們能夠選幾種學習一下。

針對模型數據,我還想說一點,由於JSON短小精悍,因此比較適合網絡傳輸。將來它可能成爲最適合WebGL的模型數據格式,因此不少的框架都開始支持JSON格式的模型數據。

     
相關文章 相關文檔 相關課程



深度解析:清理爛代碼
如何編寫出擁抱變化的代碼
重構-使代碼更簡潔優美
團隊項目開發"編碼規範"系列文章
重構-改善既有代碼的設計
軟件重構v2
代碼整潔之道
高質量編程規範
相關文章
相關標籤/搜索