最终開始WebGL的演示样例了,......
開始
使用WebGL的步骤,非常easy:
1. 获得WebGL的渲染环境(也叫渲染上下文)。
2. 发挥你的想象力,利用《WebGL參考手冊》中的函数,參考《OpenGL ES 2.0编程指南》和各种已有的WebGL演示,针对获得的WebGL渲染环境进行操作,表达出你的意境。
为了获得WebGL的渲染环境,或者说,为了给WebGL一个渲染环境,我们须要在Web页面中定义一个名称为“canvas ”的HTML5元素。每一个canvas元素都能够相应一个WebGL渲染环境。注意,我说的是“能够相应”,而不是“相应”或“必须相应”;这是由于canvas元素还能够用作Web2D的渲染环境。——假设在一个页面中定义了多个canvas元素,我们就能够同一时候运行多个WebGL渲染。
定义canvas元素不难,只是要记得给它指定一个id,以方便我们在js中对它进行訪问。另外,因为要在js中使用向量和矩阵等相关的操作,还要引入一个js文件“glMatrix-0.9.5.js”。该文件能够从网络下载,后面的版本可能会有所不同。做完这两项工作后,我的HTML文件的完整代码例如以下:
<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设置为1、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)。