WebGL自学教程——WebGL演示样例:開始

 

    最终開始WebGL的演示样例了,......javascript

開始html

 

    使用WebGL的步骤,很是easy:java

    1. 得到WebGL的渲染环境(也叫渲染上下文)。node

    2. 发挥你的想象力,利用《WebGL參考手冊》中的函数,參考《OpenGL ES 2.0编程指南》和各类已有的WebGL演示,针对得到的WebGL渲染环境进行操做,表达出你的意境。web

    为了得到WebGL的渲染环境,或者说,为了给WebGL一个渲染环境,咱们需要在Web页面中定义一个名称为“canvas ”的HTML5元素。每个canvas元素都可以相应一个WebGL渲染环境。注意,我说的是“可以相应”,而不是“相应”或“必须相应”;这是因为canvas元素还可以用做Web2D的渲染环境。——假设在一个页面中定义了多个canvas元素,咱们就可以同一时候运行多个WebGL渲染。编程

    定义canvas元素不难,只是要记得给它指定一个id,以方便咱们在js中对它进行訪问。另外,由于要在js中使用向量和矩阵等相关的操做,还要引入一个js文件“glMatrix-0.9.5.js”。该文件可以从网络下载,后面的版本可能会有所不一样。作完这两项工做后,个人HTML文件的完整代码例如如下:canvas

<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=gb2312">
<script type="text/javascript" src="glMatrix-0.9.5.js"></script>
</head>
<body>
    <canvas id="myCanvas" style="border:1px solid red;" width='600px' height='450px'></canvas>
</body>
</html> 数组

    细致看,我还给canvas元素指定了一个像素的红色边框,这在没有WebGL渲染或渲染失效的状况下,易于观察咱们所要操做的区域。我还指定了该元素的宽度为600像素,高度为450像素。由这个宽度和高度所造成的元素的区域,就是咱们可以操做的WebGL渲染区域。只是,相同请注意,我说的仍是“可以”,因为真正可实际操做的WebGL渲染区域是需要咱们使用WebGL API进行设置的。这个元素区域是咱们可以设置的最大有效区域。浏览器

    得到WebGL的渲染环境,直接调用canvas元素的getContext("webgl")方法就能够。只是要注意的是,由于眼下WebGL还处于实验室阶段,传入的參数多是“experimental-webgl”。详细的代码例如如下:网络

    var myCanvasObject = document.getElementById('myCanvas');
    var webgl = myCanvasObject.getContext("experimental-webgl");

    注意,为了方便演示,如无必要,我不会写容错性的代码。

    假设没有发生错误的话,webgl就是WebGL的渲染环境。必须说明且你必须记住,不论什么对WebGL函数、常量等的调用,都需要经过渲染环境进行,如webgl.viewport(...)、webgl.VERTEX_SHADER(此处的webgl就是myCanvasObject.getContext("experimental-webgl")的返回值)。一般,WebGL应用不是几个函数就搞定的;也就是说,webgl这个渲染环境需要在N多个地方使用,为便于訪问,我把它存储在全局变量中。在本章的演示样例中,WebGL相关的初始化无需用户交互,为方便起见,我放在了body的onload事件中(如无必要,之后的演示样例也会如此),因而,个人HTML文件的内容变成了如下这个样子:

<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=gb2312">
<script type="text/javascript" src="glMatrix-0.9.5.js"></script>
<script>
    var webgl = null;

    function Init()
    {
        var myCanvasObject = document.getElementById('myCanvas');
        webgl = myCanvasObject.getContext("experimental-webgl");
    }

</script>
</head>
<body onload='Init()'>
    <canvas id="myCanvas" style="border:1px solid red;" width='>600px' height='450px'></canvas>
</body>
</html>

    如下进行一些实际的事情。

    首先,设定WebGL的渲染区域(视见区):webgl.viewport(0, 0, width, height)。该渲染区域,并不是你要直接操做(也不能、不该当操做;除非你的是WebD3D(若是有的话)之类的玩意)的区域(即便看起来很是像)。这点和2D渲染不一样。该区域事实上是一个终于结果的显示区域。当你在WebGL内部画好图像以后,WebGL会本身主动经过某种方式,将其映射到这个区域中。好比,若是咱们指定视见区的宽和高都是10px;在WebGL中,咱们画了一个5px*20px的图像(若是可以的话);那么,终于你看到的可能并不是原图中5px*10px的某部分,而多是5px*20px这整幅图像被“拉伸”到10px*10px以后的效果。“拉伸”仅仅是对前面句子“经过某种方式”中的“方式”一词的一种形象的说法,并不许确;它究竟怎样进行,这个是着色器的事情。

    WebGL中有两种着色器:顶点着色器和片断着色器。当中,顶点着色器用来处理顶点的位置;片断着色器用来处理顶点的颜色。什么是顶点?简单说,顶点就是定义了你要绘画的那些图像上的点。比方,两个点A和B可以肯定一条线段,那么,在3D中,咱们就说,A和B是线段AB的顶点。

    着色器用WebGL函数createShader()建立。该函数接收一个參数,用来指定要建立的着色器的类型。该类型是一个枚举量,顶点着色器用WebGL枚举FRAGMENT_SHADER表示, 片断着色器用WebGL枚举VERTEX_SHADER表示。尽管你可以直接指定一个和枚举量相等的整数做为传入參数,只是在不一样的浏览器上,这些整数值可能不一样,关键是很差记,easy出错,因此,仍是建议使用枚举量。

    建立好着色器后,你还需要指定它们将3D内容转换到视见区的“方式”。该“方式”是一个字符串,由于它由符合WebGL着色语言语法的语句组成的,所以咱们称之为源代码。指定着色器的源代码调用WebGL函数shaderSource(shaderObject, sourceCode)就能够。此处有个算不上是技巧的技巧。在不使用技巧以前,设置源代码的js语句应当是这个样子:

        var source = "attribute vec3 v3Position;void main(void){gl_Position = vec4(v3Position, 1.0);}";
        webgl.shaderSource(shaderObject, source);

    假设你略微思考一下,就可能发现不爽的地方。通常,顶点着色器要运行的动做是复杂的,需要编写的着色语言语句也是很多的。直接用字符串提供的话,不方便改动和阅读。所以,咱们有必要利用一下HTML,让那些着色语句以直观的、格式化的形式显示在HTML的源代码之中,但不显示在终于的页面中。比方,咱们可以将着色语句放置在一个样式指定为不可见不显示的块元素中。但另外一种更好的作法,就是将着色语句写在一个单独的script标记中,如如下这般(script标记中的type属性可有可无;如下演示样例中的设置仅仅是为了好看。固然,假设你聪明点的,可以用它来决定该script所相应的着色器的类型;只是在这样的状况下,将typ设置为一、2或a、b,或者把type替换为其它不论什么属性,比方xyz,效果都是同样的。惟一要注意的就是,你在进行推断时使用的属性和值必须和此处所指定的一样):

        <script id="shader-vs" type="x-shader/x-vertex">
        attribute vec3 v3Position;
        void main(void)
        {
            gl_Position = vec4(v3Position, 1.0);
        }
        </script>

        <script id="shader-fs" type="x-shader/x-fragment">
        void main(void)
        {
            gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
        }
        </script>

    源代码的设置语句也要做出对应改动。首先,获取script中的内容,这儿有一个现成的js函数,例如如下:

        function ShaderSourceFromScript(scriptID)
        {
            var shaderScript = document.getElementById(scriptID);
            if (shaderScript == null) return "";

            var sourceCode = "";
            var child = shaderScript.firstChild;
            while (child)
            {
                if (child.nodeType == child.TEXT_NODE ) sourceCode += child.textContent;
                child = child.nextSibling;
            }

            return sourceCode;
        }

    而后,将获取到的内容(已是一个字符串)设置为着色器的源代码:

        webgl.shaderSource(vertexShaderObject, ShaderSourceFromScript("shader-vs"));
        webgl.shaderSource(fragmentShaderObject, ShaderSourceFromScript("shader-fs"));

    OK,着色器有了,源代码也有了,你应当也想到了,既然是源代码,是需要编译的吧?确实,着色语言看起来有点象C,某些行为也象C,需要编译和连接。

    着色器源代码的编译使用WebGL函数compileShader(shaderObject)。编译过程当中,相同可能碰到一些诸如语法错误之类的问题。所以,在编译以后,需要检查一下编译的结果状态。假设编译没有成功,则WebGL渲染就不能正常进行。检查编译状态使用WebGL函数getShaderParameter(shaderObject, WebGL.COMPILE_STATUS)。该函数返回一个布尔值,true表示编译成功,false表示有错。要注意,有错,不必定就是语法错误,还多是着色器中使用的资源超出了浏览器所能支持的范围,就象C程序中没法为某个数组分配4G大小的内存那样。

    接下来是连接。连接需要用到一个新的对象:程序对象。程序对象的建立使用WebGL函数createProgram()。在连接以前,需要确保着色器对象已附加到程序对象;附加着色器经过WebGL函数attachShader(programObject, shaderObject)进行。此处要说明的是,附加操做可以在连接以前的不论什么时刻进行,不用管着色器对象是否已经经过编译、是否已经指定了源代码,仅仅要它知足一个条件,就是一个程序对象仅仅能且必须附加一个顶点着色器和一个片断着色器。连接经过WebGL函数linkProgram(programObject)进行。它会做不少事情,详细信息可參考《OpenGL ES 2.0 编程指南》的《第四章 着色器和程序/程序的建立和连接》和其它相关章节,在此不做多述。相同,连接也会有成功和失败,这可经过调用WebGL函数getProgramParameter(programObject, WebGL.LINK_STATUS)得到。

    最后,还要使用WebGL函数useProgram(programObject)来指定WebGL使用哪一个程序对象进行渲染。

    但要让WebGL渲染出东西来,咱们还要向WebGL提供数据。在我进行简单地复述解说以前,你最好首先看一遍《OpenGL ES 2.0 编程指南》的《第六章 顶点属性、顶点数组和缓冲对象》

    WebGL中,有两种顶点数据:一种是常量数据,一种是数组数据。所谓常量数据,就是指顶点的某个属性(如,颜色)值恒定不变。而数组数据是指,顶点的某个属性(如,位置)并非恒定不变,而是由一系列不全然一样的值组成;咱们将这些值组合成一个数组。换言之,数组中每个元素都相应着该属性的一个可能值。注意,此处说的是可能值,因为在一些时候,咱们会跳过数组中的一些值。当进行渲染的时候,WebGL会依照咱们指定的规则枚举数组中的元素,将枚举到的元素的值传递到着色器中的相应变量中。

    本章演示样例,仅仅是简单地显示一个三角形,所需的数据是三个顶点的位置。由于这三个位置各不一样样(不然也不会组成一个三角形),因此咱们需要使用数组数据。

    首先,咱们用js中的数组定义好这个三个顶点位置。要注意的是,WebGL中的坐标系的范围是[-1, +1]。在没有使用坐标转换以前,咱们定义的坐标范围不能超出这个范围,不然就会显示不对。另外,咱们是在3D中画三角形,所以,使用的是三维坐标(固然,你也可以使用四维坐标)。

        var jsArrayData = [
         0.0,   1.0,   0.0,//上顶点
        -1.0,  -1.0,   0.0,//左顶点
         1.0,   0.0,   0.0];//右顶点

    你发现,jsArrayData仅仅是很是普通的一维数组,共同拥有9个元素,每三个为一组,每组和一个顶点位置相应。WebGL没法直接訪问js中的数据,咱们要经过必定的方法将这些数据弄到WebGL可以訪问的地方。这需要借助WebGL提供的API函数,过程例如如下:

    1. 使用WebGL函数createBuffer()建立一块WebGL可以訪问的存储区(咱们称之为缓冲)。

    2. 将建立的存储区设置为对应存储区类型的当前操做对象,这经过WebGL的缓冲绑定函数bindBuffer(WebGL.ARRAY_BUFFER, buffer)完毕。该函数的第一个參数表示要设置的存储区类型,可以为WebGL.ARRAY_BUFFER和WebGL.ELEMENT_ARRAY_BUFFER;前者表示绑定的缓冲为顶点数组数据,后者表示绑定的缓冲为顶点元素数组数据(对于它是啥,可以參考《OpenGL ES 2.0 编程指南》,或者耐心等待我在后面章节的复述解说)。

    3. 将js中的数据“拷贝”到WebGL缓冲中:WebGL函数bufferData(WebGL.ARRAY_BUFFER, new Float32Array(jsArrayData), WebGL.STATIC_DRAW)。该函数的第一个參数和bindBuffer意义一样。第二个參数就是咱们要拷贝的数据了。仅仅只是,咱们需要将js中的数组略微处理一下,转换为WebGL可以识别的数据格式:ArrayBuffer或ArrayBufferView。当中,ArrayBufferView又有下面几种子类型:

        . Int8Array
        . Uint8Array
        . Int16Array
        . Uint16Array
        . Int32Array
        . Uint32Array
        . Float32Array
        . Float64Array

    要注意,这些类型是为了WebGL而产生的浏览器内建类型;咱们可以在js中直接使用它们。它们的一个主要做用,就是将js中的数组转换为WebGL可以识别的数据格式。转换方式就和上面见到的那般简单,仅仅要将js数组做为參数传递给构造函数就能够。

    bufferData的第三个參数指定缓冲的使用方法,对于它的详细解说,你可以參考《OpenGL ES 2.0 编程指南》,或者耐心等待之后的章节。

    数据到此就设置完了,接下来就应当让WebGL运行画图了。

    画图有两种方式,一个是依据实际传入的顶点数组数据画图,另外一种是依据传入的顶点元素数组数据画图。此处咱们用的是前者,后者在之后的章节复述解说,或者你也可以參考《OpenGL ES 2.0 编程指南》。

    不管是顶点数组数据仍是顶点元素数组数据,在一个WebGL应用中,通常会同一时候有N个。那么在画图的时候,咱们就首先需要告诉WebGL,咱们要用的是哪一个。此操做和上面设置js数据到WebGL中一样,都是经过WebGL函数bindBuffer来完毕。但是,光这样还不行,因为咱们还需要让WebGL把顶点数组数据和着色器中的变量联系起来;仅仅有这样,着色器才干訪问到咱们设置给它的顶点数据(位置,颜色等)。这需要两个步骤:

    1. 将着色器中的变量(必须是attribute变量)关联到一个属性索引,使用WebGL函数bindAttribLocation(programObject, positionIndex, "shaderAttributeName")。在本章演示样例中,第二个參数为0;第三个參数属性名称为“v3Position”。注意,该操做必须在程序对象连接前进行(这点,你将在终于的演示样例中看到),不然无效。

    2. 使用WebGL函数enableVertexAttribArray(positionIndex)启用对应关联索引上的数组数据或元素数组数据。

    3. 经过如下的WebGL函数,指定关联索引上的数组数据或元素数组数据的正确信息:

        void vertexAttribPointer(GLuint positionIndex, GLint size, GLenum type,
                 GLboolean normalized, GLsizei stride, GLintptr offset);

    当中,size之单个数据的大小。比方,顶点的位置咱们通常用(x,y,z)表示,则,此值为3;顶点的纹理坐标用(s,t)表示,则此值为2。type指定数据的类型,可以为WebGL的BYTE、UNSIGNED_BYTE、SHORT、UNSIGNED_SHORT、FLOAT、FIXED等。normalized指定数据转换为浮点型时,是否需要规范化。stride指定相邻的两个数据之间的间隔(详解參考《OpenGL ES 2.0 编程指南》的《第六章 顶点属性、顶点数组和缓冲对象/顶点数组》)。offset指定起始数据的偏移,以字节为单位。

    画图的最后一步工做,是调用WebGL函数drawArrays(mode, first, count)或drawElements(mode, count, type, offset)运行图形绘画。这两个函数的第一个參数mode,指定绘画的模式,有点、线、三角形、三角扇等(详细有哪些及其效果,请參考《OpenGL ES 2.0 编程指南》的《第七章 基元集和光栅化》)。函数drawElements相同在之后复述介绍,或自行參考《OpenGL ES 2.0 编程指南》的相关章节。函数drawArrays的第二个參数指定起始顶点的索引。第三个參数指定要绘画的顶点的个数。在本节演示样例中,咱们的绘画模式为三角形,起始点是0,绘画个数是3(因为三角形仅仅能有三个顶点)。注意,为了确保正确,你须要在每帧渲染開始以前进行必须要的擦除:首先使用WebGL函数clearColor(red, green, blue, alpha)等设置擦除信息,而后调用WebGL函数clear(WebGL.COLOR_BUFFER_BIT)运行擦除。

    现在,把上面的一切结合到一块儿,整个HTML源代码例如如下:

<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=gb2312">
<script type="text/javascript" src="glMatrix-0.9.5.js"></script>

        <script id="shader-vs" type="x-shader/x-vertex">
        attribute vec3 v3Position;
        void main(void)
        {
            gl_Position = vec4(v3Position, 1.0);
        }
        </script>

        <script id="shader-fs" type="x-shader/x-fragment">
        void main(void)
        {
            gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
        }
        </script>

<script>
        function ShaderSourceFromScript(scriptID)
        {
            var shaderScript = document.getElementById(scriptID);
            if (shaderScript == null) return "";

            var sourceCode = "";
            var child = shaderScript.firstChild;
            while (child)
            {
                if (child.nodeType == child.TEXT_NODE ) sourceCode += child.textContent;
                child = child.nextSibling;
            }

            return sourceCode;
        }

    var webgl = null;
    var vertexShaderObject = null;
    var fragmentShaderObject = null;
    var programObject = null;
    var triangleBuffer = null;
    var v3PositionIndex = 0;

    function Init()
    {
        var myCanvasObject = document.getElementById('myCanvas');
        webgl = myCanvasObject.getContext("experimental-webgl");

        webgl.viewport(0, 0, myCanvasObject.clientWidth, myCanvasObject.clientHeight);

        vertexShaderObject = webgl.createShader(webgl.VERTEX_SHADER);
        fragmentShaderObject = webgl.createShader(webgl.FRAGMENT_SHADER);

        webgl.shaderSource(vertexShaderObject, ShaderSourceFromScript("shader-vs"));
        webgl.shaderSource(fragmentShaderObject, ShaderSourceFromScript("shader-fs"));

        webgl.compileShader(vertexShaderObject);
        webgl.compileShader(fragmentShaderObject);

        if(!webgl.getShaderParameter(vertexShaderObject, webgl.COMPILE_STATUS)){alert("error:vertexShaderObject");return;}
        if(!webgl.getShaderParameter(fragmentShaderObject, webgl.COMPILE_STATUS)){alert("error:fragmentShaderObject");return;}

        programObject = webgl.createProgram();

        webgl.attachShader(programObject, vertexShaderObject);
        webgl.attachShader(programObject, fragmentShaderObject);

        webgl.bindAttribLocation(programObject, v3PositionIndex, "v3Position");

        webgl.linkProgram(programObject);
        if(!webgl.getProgramParameter(programObject, webgl.LINK_STATUS)){alert("error:programObject");return;}

        webgl.useProgram(programObject);

        var jsArrayData = [
        0.0, 1.0, 0.0,//上顶点
        -1.0, -1.0, 0.0,//左顶点
        1.0, 0.0, 0.0];//右顶点

       triangleBuffer = webgl.createBuffer();
       webgl.bindBuffer(webgl.ARRAY_BUFFER, triangleBuffer);
       webgl.bufferData(webgl.ARRAY_BUFFER, new Float32Array(jsArrayData), webgl.STATIC_DRAW);

       webgl.clearColor(0.0, 0.0, 0.0, 1.0);
       webgl.clear(webgl.COLOR_BUFFER_BIT);

       webgl.bindBuffer(webgl.ARRAY_BUFFER, triangleBuffer);

       webgl.enableVertexAttribArray(v3PositionIndex);

       webgl.vertexAttribPointer(v3PositionIndex, 3, webgl.FLOAT, false, 0, 0);

       webgl.drawArrays(webgl.TRIANGLES, 0, 3);

    }
</script>
</head>
<body onload='Init()'>
    <canvas id="myCanvas" style="border:1px solid red;" width='600px' height='450px'></canvas>
</body>
</html>

    你可以将上面的源代码拷贝到一个HTML文件里,而后在支持WebGL的浏览器中执行。在我使用的FF浏览器上,执行结果例如如下:

从图中,你可以判断出WebGL x和y轴的坐标系统,左下角为(-1, -1), 右上角为(1, 1)。

相关文章
相关标签/搜索