Web 技术研究所

我一直坚信着,Web 将会成为未来应用程序的主流

WebGL(玖) 绘制球体

  之前的文章中都是用立方体作为例子,因为它简单。如果要绘制一个曲面,比如球体,就比较麻烦。就像2D绘图中曲线是用直线来逼近一样,曲面也是用平面来逼近的。球体不像立方体那样36个顶点就能搞定的,一个表面比较平滑的球体需要上千个顶点才能绘制出。
  绘制曲面最关键的就是顶点的构造了,方法并不只有一种。如果你不觉得蛋疼你可以把球体当作富勒烯来构造顶点,不过这个就太麻烦了。我们通常会使用最简单的方法,也就是使用球体的参数方程。使用参数方程还有个优点就是方便贴图,在参数方程中我们很容易把坐标映射到平面上。直接拿出方程来用有些人可能会搞不清原理吧,我们就来推导一次。我们使用的坐标系并不是笛卡尔坐标系,所以最终结果会和标准的球体方程不同,不过不影响使用。另外,椭球的情况我就不说了,只要搞明白球体的绘制原理自然会明白。
  首先,我们要把一个球体在y轴(y轴是球体主视图的高方向)上切出n个横截面,就像地球仪上的纬线那样一层层的切。如果你有观察过地球仪你就会发现每一条纬线之间在球面上的弧长是相等的。这就意味着在主视图上它们是一条半径绕着圆心旋转了半圈,每次旋转一样的角度。我们切这个横截面也是使用同样的方法,切出的横截面在xz平面上都是圆,这些圆的半径可以根据切纬线的角度计算出来。有了这个半径,我们又可以把这个横截面的圆切成n个弧(参考地球仪上的经线),也是一样的方法,拿出个半径来旋转,只不过这个回旋转的是一周而不是之前的半周。也许这样描述还是有点难理解,看下面的图

  根据这个图,我们就可以得到{x,y,z}关于{a,b}的坐标,只要遍历{a,b}就可以得到球面上所有的点,写成方程就是球体的方程。但是我们需要的不是球体的方程,而是在程序中的计算。既然要遍历两个坐标,我们就使用二重循环。至于遍历的次数n这个就是自己给定的,n的值越大我们就会得到越多顶点,绘制出的球体就越平滑,但是性能开销也就越大。
  虽然现在我们是可以遍历整个球面上的坐标了,但是我们的图元是三角形的,还需要根据这些坐标把它们连成三角形才行。怎么连呢?我们之前使用的立方体每个面都是矩形吧?我们会用两个三角形去拼出这个矩形。而球面上根据经纬度来切就可以切成很多四边形(极点可以看作是许多个点在同一个位置),既然我们知道如何用三角形拼出矩形,要拼出四边形也是一样的(其实这也是黎曼矩形)。我们只要从遍历到的每个点中找到和他们组成四边形的其它点就可以。这很简单,假如有0到n个点,我们从1开始遍历到n。{a,b}、{a-1,b}、{a,b-1}、{a-1,b-1},这个四个顶点就是我们找到的四边形。下面是本篇例子中计算球体的代码
//第一个参数n是循环次数,也就是绘制球体的精细程度
//后面的参数是数组引用
count=(function(n,po,mp,no){
  var i,j,k,f=function(a,b){
    var a=Math.PI*a/n,b=2*Math.PI*b/n,l=Math.sin(a);
    return [Math.sin(b)*l,Math.cos(a),Math.cos(b)*l];
  }; //f是球体方程的函数
  for(i=1;i<=n;i++)for(j=1;j<=n;j++){ //二重循环遍历球体方程的两参数
    //这里我就不用索引了,直接把每个四边形需要的6个顶点都用上
    k=[].concat(f(i,j),f(i-1,j),f(i,j-1),f(i,j-1),f(i-1,j),f(i-1,j-1));
    //po是顶点坐标数组
    po.push.apply(po,k);
    //no是顶点的法向量,光照效果会用到
    //单位球体的顶点法向量就是球心在原点上时的顶点坐标
    no.push.apply(no,k);
    //这个是球面上贴图的坐标
    mp.push(
      j/n,n-i/n, j/n,n-(i-1)/n, (j-1)/n,n-i/n,
      (j-1)/n,n-i/n, j/n,n-(i-1)/n, (j-1)/n,n-(i-1)/n
    );
  };
  return n*n*6; //返回顶点个数,绘制的时候要用到
})(36,po_dat=[],mp_dat=[],no_dat=[]);
  顶点坐标和法向量都没啥问题的吧?这里要说的只有贴图坐标。球体的方程有两个参数,第一个参数是遍历球的y方向半弧(看上面图片主视图中红色部分),第二个参数是遍历球在xy平面的周长(看上面图片俯视图的红色部分)。要把一张图片贴到球面上,这个图片的高显然就是第一个参数所遍历的弧,而图片的宽显然就是球在xy平面上的周长。也就是说,球体参数方程的两个参数正好是贴图时用的图片坐标,所以我一开始才说这个方法贴图方便。但是要注意一点,我们遍历第一个参数a时候从0到π变化,y的值是从1变化到-1的,也就是说y是越来越小的。而贴图时使用的y轴是从0到1的,我们不能直接把a的0~π对应到贴图的0~1上,而是应该对应到贴图的1~0上,否则图片会倒置。所以上面的代码中我在使用外层循环i作为贴图坐标时都是使用的n-i/n。
  这个例子我没有使用索引,所以最后的绘制是使用drawArrays而不是drawElements,如果有需要也可以自己生成个索引来绘制,那样可以节省一些显存吧。那就是优化方面的事情了。
  
  这篇文章的例子使用的东西比较多,代码会比较长。有贴图和光照的效果,这些都是之前的文章说过的,如果不明白可以回去翻翻。球体是最容易计算的曲面物体了,至于其他形状奇怪的物体,只要推出参数方程就很容易得到顶点数组。而且对于任意曲面,法向量的计算可以把方程写成隐函数的形式对其求梯度,得到的向量场就是法向量场。只要带入坐标就可以求出曲面上任意点的法向量了。不过要把它转换成单位法向量还需要把它除以自身的模。至于贴图的坐标计算,这就要看你的图片需要怎么贴,从自己的需求出发,在曲面上找到一个平面,把坐标映射到贴图的二维坐标上就行了。既然会绘制球体就应该可以举一反三的绘制出各种曲面物体。其他的我就不说了, 最后是球体的完整例子

  WebGL绘制球体
网名:
34.203.245.*
电子邮箱:
仅用于接收通知
提交 悄悄的告诉你,Ctrl+Enter 可以提交哦
神奇海螺
[查看全部主题]
各类Web技术问题讨论区
发起新主题
本模块采用即时聊天邮件通知的模式
让钛合金F5成为历史吧!
次碳酸钴的技术博客,文章原创,转载请保留原文链接 ^_^