Web 技术研究所

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

WebGL(贰拾贰) 复杂的鼠标操作

  在第拾肆篇中有介绍过最简单的鼠标操作,当时只是使用了坐标还原的方法。但是一旦场景复杂了,这个方法就不给力了。比如摄像机不是固定的情况,如果在CPU中做投射矩阵和视图矩阵的逆,效率就非常低。于是这篇咱就来介绍高级的鼠标操作,使用了帧缓冲。
  如果对之前实现阴影效果的原理很熟悉,那么这篇文章的原理自己就可以推导出来。以前的做法是把平面的鼠标坐标映射到三维空间中再做碰撞测试,因为鼠标坐标只有一个,只需要做一次逆变换就可以得到,这不会消耗太多资源。但是场景复杂之后,要把鼠标变换到三维空间中就不太容易了。而且对于大量的物体,逐个做碰撞测试是非常没效率的。这时候我们就可以考虑把三维空间中的物体变换到平面上再做碰撞测试。也就是说,把复杂的计算放在GPU中完成,反正GPU是个二愣子,这种体力活当然交给它来做了。CPU需要做的只是从显存中取出GPU的运算结果,然后和鼠标坐标比对,就这么简单。
  概括起来是挺简单的,但是我实现了下也写了一百多行代码= =!首先,我们要给场景中的对象编号,或者说把场景中要绘制的对象全部放入一个数组中。绘制时候先绘制到帧缓冲中,这时需要把物体的编号转换成颜色,并关闭光照效果等其它不需要和鼠标交互的效果。要保证每个物体绘制到帧缓冲中时都只有一种颜色,这样绘制完成之后我们才能用颜色来区分物体。或者说从颜色数据中读取物体的编号。这个读取的过程就是从帧缓冲关联的数据贴图中读取数据,这要用的一个以前没说过的函数readPixels。如果只是做鼠标坐标的判断,那么只要从贴图数据中读取鼠标所在位置的颜色就够了。如果能从这个颜色信息中解析出它属于哪个对象,那就说明目前鼠标指针正在这个对象上。然后要对这个对象做什么操作就看自己具体的需求了。
  这个程序的着色器代码比较简单,我就不另外说明了。还有帧缓冲、摄像机、等。这些东西是基础,如果没搞明白就先回去看之前的文章。下面是这个程序的关键部分的代码。
//动画主过程
(function(){
  //绘制到帧缓冲
  webgl.bindFramebuffer(webgl.FRAMEBUFFER,shader.pFramebuffer);
  webgl.clear(webgl.COLOR_BUFFER_BIT|webgl.DEPTH_BUFFER_BIT);
  webgl.uniform1i(shader.isPicking,true);
  webgl.disable(webgl.BLEND);
  webgl.enable(webgl.DEPTH_TEST);
  shader.drawObjects(1);
  //读取鼠标位置颜色
  var color=new Uint8Array(4);
  webgl.readPixels(
    MX,HEIGHT-MY,1,1,webgl.RGBA,webgl.UNSIGNED_BYTE,color
  );
  if(color[3]){
    if(shader.objects.picked)
      shader.objects.picked.color=shader.objects.picked.defaultColor;
    shader.objects.picked=shader.objects[
      Math.round(color[0]/255*shader.objects.length)
    ];
    shader.objects.picked.color=[1,0,0,0.6];
  };
  //绘制到屏幕
  webgl.enable(webgl.BLEND);
  webgl.disable(webgl.DEPTH_TEST);
  webgl.bindFramebuffer(webgl.FRAMEBUFFER,null);
  webgl.clear(webgl.COLOR_BUFFER_BIT|webgl.DEPTH_BUFFER_BIT);
  webgl.uniform1i(shader.isPicking,false);
  shader.drawObjects(0);
  //下一帧
  shader.camera.doActions();
  time+=0.01;
  requestAnimationFrame(arguments.callee);
})();
  先绘制到帧缓冲,再绘制到屏幕,这都很简单。需要特别说明的是这两个步骤之间的东西。readPixels这个方法是从当前工作贴图中读取数据,前两个参数是贴图上的位置,单位是像素。我的场景宽高是400的,绑定的贴图由于需要是2的n次方,所以用了512。其实真正用到的大小只有400,所以这个头两个参数是从0到400的坐标。另外还要注意坐标系,贴图是在坐标系的第一象限上。而鼠标事件获取的坐标y是逆的(因为屏幕的原点在左上角),所以这里的y坐标需要做个调整。接着的两个参数是读取的区域大小,单位依然是像素。我的程序只是判断鼠标的位置所以只要读取一个像素就足够了。再随后两个参数是像素的颜色类型和数据类型,这没啥好说的吧。数据类型貌似是固定的,我也试过使用其它值,但是就会抛出异常,所以UNSIGNED_BYTE不用改。最后的参数是接收返回数据的数组指针,既然是数组指针就需要有一块连续的内存,所以这个变量需要使用强类型数组,也就是上一个参数指定的类型数组。数组的空间也需要事先分配好,比如我的程序中获取一个像素,类型是RGBA(4个通道),所以需要1*4,总共4个字节。如果经常写C语言之类的,对这种用法应该会很熟悉吧。
  取到颜色之后把颜色还原成数组的下标即可,至于怎么还原那就得看你自己在绘制时如何把下标转换成颜色了。我的程序直接把数组的下标分布到[0,1]区间上放入红色通道的,所以读取时直接从红色通道读取这个颜色。color[3]是透明通道,我绘制的颜色都是不透明的,因此只要透明通道非0就说明此处有对象,如果是0则说明鼠标不在对象上。所以才做这个判断,如果鼠标不在对象上就没必要计算颜色与数组下标了。
  其它代码也没什么特别纠结的地方,关键的只有以上这些。最后是整个程序的完整实例。


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