突袭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
代码整洁之道
高质量编程规范
相关文章
相关标签/搜索