四、绘制一个点

  上次我们介绍了如何在<canvas>中使用WebGL,以及几个基础的WebGL函数;实现了背景色的重置;为了扩展方便,我们把上次的代码做了些改动,将绘制图形的js独立成文件,这样我们只关注与这个js文件的编写;以后除非HTML文件发生变化,我们就跳过它,直接讨论JavaScript代码。

 1 <!doctype html>
 2 <html>
 3 <head>
 4     <meta charset="UTF-8">
 5     <meta name="Generator" content="EditPlus®">
 6     <meta name="Author" content="Mirror">
 7     <meta name="Keywords" content="">
 8     <meta name="Description" content="">
 9     <title>Hello Point</title>
10     <!--《WebGL编程指南》的作者为读者编写的WebGL辅助函数-->
11     <script src="lib/webgl-utils.js"></script>
12     <script src="lib/webgl-debug.js"></script>
13     <script src="lib/cuon-utils.js"></script>
14     <!--JavaScript文件,在<canvas>中绘制图形-->
15     <script src="lib/hello-point.js"></script>
16 </head>
17 <body onload="main()">
18     <!--定义<canvas>标签,通过width属性和height属性规定它是一片400×400的绘制区域-->
19     <canvas id="myCanvas" width="400" height="400">
20         <!--当浏览器不支持时,会直接忽略<canvas>标签,而直接显示下面这一行提示-->
21         Please use the browser supporting "canvas".
22     </canvas>
23 </body>
24 </html>
Hello point.html

  接下来,我们在此基础上,绘制一个位于原点(0.0,0.0,0.0)处的10个像素大的红色的点。因为使用的是三维图形上下文,所以指定这个点时需要使用三维坐标。坐标系统后面介绍,这里只需要理解为原点位于<canvas>中心位置。效果如如下:

 

  实际上,我们使用矩形而不是圆来绘制一个点,因为绘制矩形比绘制圆更快;就像在前一次中我们以RGBA的形式指定了背景色一样,这里也需要同样的处理;在前面,我们使用2d上下文来绘制了一个矩形;先指定了绘图颜色,然后进行绘制。你可能认为WebGL也差不多,不幸的是,没那么简单。WebGL依赖于一种新的称为着色器的绘图机制。着色器提供了灵活且强大的绘制二维或三维图形的方法,所有WebGL必须使用它。正因为强大,所以更复杂。

  要使用WebGL绘图,必须使用着色器,哪怕是一个点(矩形);着色器程序是以字符串的形式“嵌入”在JavaScript文件中,并且在程序开始运行前就已经设置好了。WebGL需要使用两种着色器:顶点着色器、片源着色器;下面分别介绍:

  顶点着色器:顶点着色器是用来描述顶点特性(如位置、颜色等)的程序。顶点是指二维或三维空间中的一个点,比如二维或三维图形的端点或交点。

  片元着色器:进行逐片元处理过程(如光源)的程序。片元是一个WebGL术语,你可以将其理解为像素(图像的单元)。

  在后续,我们会详细的学习着色器。简单的说,在三维场景中,仅仅用线条和颜色把图形画出来是远远不够的。你必须考虑如光线照上去后,或者观察者的视角发生变化时,对场景会有什么影响。着色器可以高度灵活的完成这些工作;提供各种渲染效果。这也就是现在制作的三维场景如此逼真的原因。

  

  上图显示了WebGL系统的执行流程;左侧为浏览器,首先执行JavaScript程序,调用了WebGL的相关方法,然后顶点着色器和片元着色器就会执行,顶点着色器指定绘制图形的位置和尺寸;片元着色器则指定绘制图形的颜色;然后在颜色缓冲区内进行绘制,这时就清空了绘图区,最后,颜色缓冲区中的内容就自动在浏览器的<canvas>标签上显示出来。

  回到我们今天的目标来,下面显示了hello-point.js的代码。

 

 1 //顶点着色器程序
 2 var VSHADER_SOURCE =
 3     "void main() { \n" +
 4     //设置坐标
 5     "gl_Position = vec4(0.0, 0.0, 0.0, 1.0); \n" +
 6     //设置尺寸
 7     "gl_PointSize = 10.0; \n" +
 8     "} \n";
 9 
10 //片元着色器
11 var FSHADER_SOURCE =
12     "void main() {\n" +
13     //设置颜色
14     "gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n" +
15     "}\n";
16 
17 function main() {
18     //获取<canvas>标签。
19     var canvas = document.getElementById("myCanvas");
20     //获取WebGL绘图上下文。
21     var gl = getWebGLContext(canvas);
22     //如果浏览器不支持WebGL则提示错误。
23     if (!gl) {
24         console.log("Failed to get the rendering context for WebGL.");
25         return;
26     }
27 
28     //初始化着色器
29     if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
30         console.log("Faile to initialize shaders.");
31         return;
32     }
33 
34     //设置<canvas>的背景色
35     gl.clearColor(0.0, 0.0, 0.0, 1.0);
36 
37     //清空<canvas>
38     gl.clear(gl.COLOR_BUFFER_BIT);
39 
40     //绘制一个点
41     gl.drawArrays(gl.POINTS, 0, 1);
42 
43 }
hello-point.js

 

  这个文件包含三个部分,顶点着色器程序(GLSL ES 语言),片元着色器程序(GLSL ES 语言)和主程序(JavaScript语言)。着色器程序代码必须预先处理成单个字符串的形式,所以我们用+号将多行字符串连成一个长字符串。每一行以\n结束,这是由于当着色器内部出错时,就能获取出错的行号,这对于检查源代码中的错误很有帮助;但是,\n并不是必须的。为了更容易维护,也可以把着色器代码写到单独的文件中(就像javaScript文件一样),然后通过javaScript程序从文件中读取出来加载。

  根据程序流程,加载页面----->执行main()函数----->获取<canvas>标签----->获取绘图上下文;到这里,都跟之前的流程一样;接下来,会执行名为initShaders()的函数。这个函数是《WebGL编程指南》的作者专门写的一个辅助函数;该函数被定义在cuon.util.js中。这个函数的作用是对字符串形式的着色器进行初始化。我们来看下这个函数的具体参数定义:

  

  Web系统由两部分组成,即顶点着色器和片元着色器。在初始化着色器之前,顶点着色器和片元着色器都是空白的,我们需要将字符串形式的着色器代码从JavaScript传给WebGL系统,并建立着色器,这就是initShaders()函数要做的事情。着色器运行在WebGL系统中,而不是JavaScript程序中。

  initShaders()函数执行成功后,着色器被创建好了并随时可以使用,顶点着色器将被首先执行,它对gl_Position变量和gl_PointSize变量进行赋值,并将它们传入片元着色器,然后片元着色器再执行。实际上,片元着色器接收到的是经过光栅化(将几何图形变为二维图像的过程)处理后的片元值;现在可以简单认为这两个变量从顶点着色器传入了片源着色器。

  下面,我们来看看着色器如何画出一个点(矩形),前面提到,我们需要三个信息来画出这个点:位置,尺寸和颜色。位置和尺寸在顶点着色器中指定,和C语言一样,着色器程序必须包含一个main()函数。main()前面的关键字void表示这个函数不会有返回值;而且也不能为main()函数指定参数。着色器程序使用 = 操作符为变量赋值。gl_Position和gl_pointSize这两个变量内置在顶点着色器中,而且有着特殊的含义:前者表示顶点的位置,后者表示点的尺寸。gl_Position必须被赋值,否则着色器就无法正常工作;gl_PointSize并不是必须的,如果不赋值,着色器会为其取默认值1.0。

  和JavaScript不同,GLSL ES是一种强类型语言,也就是说,开发者需要明确指出某个变量是某种类型;类似于Java和C、C#等。在这个程序中,出现了两种数据类型,float:表示浮点数;vec4:表示由4个浮点数组成的矢量(也称作向量)。而且着色器语言没有类似Java、C#语言的类型隐式转换的功能,所以这样写:gl_PointSize = 10;就会导致错误;因为gl_PointSize 需要一个float类型的值,而10是整型。另一个内置变量gl_Position的类型为vec4,但是三维坐标只有3个数,即X,Y和Z轴的坐标值,这就需要某种方法将其转化为vec4类型的变量。着色器提供了内置函数vec4()可以完成这个事情。就如我们代码中那样使用:gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); 前3个分量对应X,Y,Z轴坐标值,那第4个分量1.0是怎么来的呢。由4个分量表示的坐标被称为齐次坐标,因为它能够提高处理三维数据的效率,所以在三维图形系统中被大量使用。当第4个分量为1.0时,这个齐次坐标就可以表示“前三个分量为坐标值”的点。

  如前所述,片元可以看成是显示在屏幕上的像素。片元着色器和顶点着色器一样,也有一个main()函数。片元着色器将点的颜色赋值给gl_FragColor,该变量是片元着色器唯一的内置变量,它控制着像素在屏幕上的最终颜色。对这个内置变量赋值后,相应的像素就会以这个颜色值显示。和顶点着色器中顶点位置一样。颜色值也是vec4类型的,分别表示RGBA的4个分量。

  建立了着色器后,我们开始进行绘制操作,我们使用gl.DrawArray()函数进行绘制。这个函数的功能非常强大,可以用来绘制各种图形,具体参数说明如下:

 

   因为我们绘制的是单独的点,所以设置第1个参数为gl.POINTS;设置第2个参数为0,表示从第1个顶点(虽然只有一个顶点)开始画起的,第3个参数为1,表示这个程序仅仅只画了1个点。当程序调用gl.DrawArray()函数时,顶点着色器将被执行count次,每次处理一个顶点,这时候顶点着色器开始执行内部的main()函数,然后设置位置和尺寸;执行完成后,片元着色器开始执行其main()函数,设置颜色;最后,一个红色的10个像素大的点就被绘制在了(0.0,0.0,0.0,1.0)处,也就是绘制区域的中心位置。

  现在,我们对顶点着色器和片元着色器的工作服方式有了大致的了解,只是有个问题,为何(0.0,0.0,0.0,1.0)会绘制到了<canvas>的中心位置?下次我们介绍坐标系统会解开这个谜题。

posted @ 2014-12-25 00:00  Mirror-PC  阅读(3001)  评论(0编辑  收藏  举报