WebGL实现单通道wireframe渲染

如果要把一个对象的线框绘制出来,一般的方法是先绘制实体对象,然后通过gl.LINES的模式再绘制一遍模型,此时模型的线框就会被绘制出来。

gl.LINES的问题

  1. 此方法需要绘制两遍对象,因此会造成性能的损失。
  2. 使用此种方式绘制线框的时候,深度值偏移是必要的。那是因为,线条的光栅化过程和多边形的光栅化过程并不是完全一致的。这就导致绘制一个多边形的边和绘制多边形本身,相同位置的片元,其深度值可能并不一样。

线段和多边形的光栅化不完全一致,为了避免z-fighting,也需要一个深度偏移。
但是,添加一个偏移并不能完美的解决问题。 这将会导致一些本该被隐藏的线段,未被遮挡。

  1. 使用gl.LINES的另外一个问题是,在WebGL中,存在一个bug,就是线宽只能设置为1。请参考以下文章:
    jianshu.com/p/cee4ce496 绘制Line的bug(一)

因此本文将会介绍一种方法,可以在一个pass内绘制对象及其线框。

原理

我们知道,一般对象都是由三角形组成的。而要显示的线框,正好是三角形的边,如果在绘制的时候,给三角形的边一个不同的颜色,便可以实现在对象上面绘制线框的效果。
那么现在的问题是,如何确定三角形的边呢?

重心坐标系

要确定三角形的变,可以使用重心坐标系。有关重心坐标的的说明,可以参考:
zh.wikipedia.org/wiki/%
对于三角形而言,重心坐标可以这样定义:
三角形所在平面上的任意一点P(笛卡尔坐标),可以通过三角形的三个顶点A、B、C(笛卡尔坐标)来表示:
P = Ax + By + C * Z,其中(x、y、z)便是重心坐标。由此可以看出P点其实是A、B、C点加权之和。
如下图所示,A点的重心坐标是(1,0,0),B点的重心坐标是(0,1,0),C点的重心坐标是(0,0,1)

重心坐标

重心坐标确定三角形的边

由上面的讲解 和图片展示可以得知,重心坐标(x,y,z)中任何一个值为0的点,都在三角形的边上。不过在实际的图形渲染中,边的宽度不可能是0,而应该是一个大于0的值,所以一般可以指定一个要绘制的线宽width,如果任何一个点的重心坐标(x,y,z)中的人一个分量的值小于这个线宽width,可以认为在边上。

代码实现

基于上面说的原理,首先需要定义顶点的重心坐标,对于一个三角形来说,可以把三个顶点的中心坐标分别指定为(1,0,0)、(0,1,0)、(0,1,0)即可。而对于一个四边形,有四个顶点 0,1,2,3,而绘制的时候使用索引 0,1,2, 2,1,3来绘制,此时可以把重心坐标定义如下:

var barycentric = [
        1,0,0,  0,1,0, 0,0, 1,  1,0,0,
   ];

然后,在着顶点色器中定义对应的attribute变量,由于重心坐标最终需要传递到片元着色器中,所以还需要对应的varying变量:

attribute vec3 aBarycentric;
    ...
    varying vec3 vBarycentric;
    void main(){
        ...
        vBarycentric = aBarycentric;
    }

然后,在片元着色器中判断,如果vBarycentric的任意一个分量的值小于指定的宽度值,则颜色为指定的线框颜色:

if(any(lessThan(vBarycentric, vec3(0.02)))){
          gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
        }
        else{
            gl_FragColor = color;
        }

通过上面代码,最终绘制的立方体效果如下:

立方体的线框

去掉锯齿

从上面的立方体绘制的效果图可以看出,线框的锯齿很严重,而且线的宽度不是一致的。这是因为,之前的判断是基于三角形表面的,通过光栅化之后,由于线条的角度等原因,最终在屏幕上面的宽度就变得不一致了。

使用fwidth方法

要线宽的判断基于屏幕,需要使用到一个方法fwidth。该方法需要WebGL 引入一个扩展之后才能使用。该扩展是:OES_standard_derivatives。
首先使用WebGL的getExtension方法获取该扩展,代码如下:

gl.getExtension("OES_standard_derivatives");

然后在shader中启用这个扩展,代码如下:

#extension GL_OES_standard_derivatives : enable

然后通过fwidth函数,把vBarycentric的值缩放到 vBarycentric在屏幕变化的范围之内,代码如下:

vec3 d = fwidth(vBarycentric);
 vec3 a3 = smoothstep(vec3(0.0), d*2.0, vBarycentric);

fwidth表示一个变量在屏幕空间的x轴变化dFdx + y轴变化dFdx之和。 其中涉及到dFdx、dFdy和fwidth的相关介绍,笔者将会在后续的文章中介绍。
在获取了基于屏幕像素空间的的重心坐标a3之后,变可以通过通过该变量来进行判断,并绘制出指定宽度的线框:

gl_FragColor.rgb = mix(vec3(0.0,0.0,0.0), vec3(1.0), min(min(a3.x, a3.y), a3.z));

通过改良之后的绘制效果如下,可以看出明细效果改进了很多:

改进的立方体线框

四边形线框

前面我们看到的都是三角形的线框,有的时候,我们希望获取四边形的线框,应该怎么处理呢? 其实此时,只需要调整下顶点的重心坐标即可,在前文中,一个四边形的四个顶点的重心坐标如下:

var barycentric = [
        1,0,0,  0,1,0, 0,0, 1,  1,0,0,
   ];

如果把四边形的四个顶点的重心坐标调整如下:

var barycentric = [
       1,0,0,  1,1,0, 0,0, 1,  0,0,0,  //前
    ];

便可以达到效果,最终绘制的效果如下图所示:

立方体线框(四边形线框)

后记

英伟达也提出过绘制线框的解决方案,参考下面链接:
developer.download.nvidia.com
不过该方案是基于Geometry Shader技术来实现的,而该技术在WebGL并未实现,所以该方案不能很好的移植到到WebGL。

原文:https://mp.weixin.qq.com/s/mjmfXPtBXVaZmg08c9Z1zQ

WEBGL学习网(WebGLStudy.COM)专注提供WebGL 、ThreeJS、BabylonJS等WEB3D开发案例源码下载。
声明信息:
1. 本站部分资源来源于用户上传和网络,如有侵权请邮件联系站长:1218436398@qq.com!我们将尽快处理。
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源打赏售价用于赞助本站提供的服务支出(包括不限服务器、网络带宽等费用支出)!
7.欢迎加QQ群学习交流:549297468 ,或者搜索微信公众号:WebGL学习网
WEBGL学习网 » WebGL实现单通道wireframe渲染

发表评论

提供优质的WebGL、ThreeJS源码

立即查看 了解详情