如果要把一个对象的线框绘制出来,一般的方法是先绘制实体对象,然后通过gl.LINES的模式再绘制一遍模型,此时模型的线框就会被绘制出来。
线段和多边形的光栅化不完全一致,为了避免z-fighting,也需要一个深度偏移。 但是,添加一个偏移并不能完美的解决问题。 这将会导致一些本该被隐藏的线段,未被遮挡。
我们知道,一般对象都是由三角形组成的。而要显示的线框,正好是三角形的边,如果在绘制的时候,给三角形的边一个不同的颜色,便可以实现在对象上面绘制线框的效果。 那么现在的问题是,如何确定三角形的边呢?
要确定三角形的变,可以使用重心坐标系。有关重心坐标的的说明 对于三角形而言,重心坐标可以这样定义: 三角形所在平面上的任意一点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。该方法需要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, //前
];
便可以达到效果,最终绘制的效果如下图所示: