突袭HTML5之WebGL 3D概述(上) - WebGL原生开发

      WebGL开启了网页3D渲染的新时代,它允许在canvas中直接渲染3D的内容,而不借助任何插件。WebGL同canvas 2D的API一样,都是通过脚本操纵对象,所以步骤也是基本相似:准备工作上下文,准备数据,在canvas中绘制对象并渲染。与2D不同的就是3D涉及的知识更多了,例如世界、光线、纹理、相机、矩阵等专业知识。WebGL有一个很好的中文教程,就是下面使用参考中的第一个链接,所以这里不再班门弄斧,后面的内容只是简单的总结一下学习的内容。

浏览器的支持
      由于微软有自己的图形发展计划,一直不支持WebGL,所以IE目前除了安装插件外,是无法运行WebGL的。其他的主流浏览器如Chrome、FireFox、Safari、Opera等,都装上最新的版本就可以了。除了浏览器要装最新的外,还要保证显卡的驱动也是最新的。
      装上这些以后,可以打开浏览器,输入下面的网址验证一下浏览器对WebGL的支持情况:http://webglreport.sourceforge.net/。

      在正常安装以上浏览器之后还是不能运行WebGL,那你可以强制开启WebGL支持试一试。开启方法如下:
Chrome浏览器
      我们需要为Chrome加入一些启动参数,以下具体操作步骤以Windows操作系统为例:找到Chrome浏览器的快捷方式,右键点击快捷方式,选择属性;在目标框内,chrome.exe后面的引号后面,加入以下内容:

--enable-webgl --ignore-gpu-blacklist --allow-file-access-from-files

点击确定后关闭Chrome,然后用此快捷方式启动Chrome浏览器。
几个参数的含义如下:
--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图形库。

 

实用参考:

开发中心:https://developer.mozilla.org/en/WebGL

你可能感兴趣的:(突袭HTML5之WebGL 3D概述(上) - WebGL原生开发)