目录
前言
基本思路
求左右端点
实现
组织数据
顶点着色器计算端点坐标
效果
前言
WebGL绘制模式有点、线、面三种;通过点的绘制可以实现粒子系统等,通过线可以绘制一些连线关系;面就强大了,通过面,我们可以绘制我们想绘制的所有的三维对象。然而,WebGL在绘制线条的时候,存在一个缺陷,那就是在一些机器的一些浏览器上面(大多数情况下)线宽只能设置为1,而不能设置成其他的值。
http://alteredqualia.com/tmp/webgl-linewidth-test/,可以测试自己的电脑是否可以绘制粗线
可以看出,我的Chrome有这个 问题
基本思路
既然画线实现不了加粗,那就使用面来模拟粗线。
如下,一条线 p0 p1 构成,可算出两个点的左右两个端点0 1 、 2 3,这四个端点组成两个三角形
求左右端点
- 二维向量(x, y)的法向量为(-y, x),为方便后续平移操作,将法向量标准化为单位向量;
- 通过平移变换将p0、p1沿上述得到法向量正向平移width/2得到点0、1,沿法向量反向平移width/2得到点2、3。
但是,这样简单粗暴的求法向量会有问题,如下图,多个线段间会有冗余分割线条
为了消除节点处的可见分割,有必要将相邻节段的相邻点组合起来,以保持相邻节段两个节段的厚度。要做到这一点,需要更进一步的求法向量
如下图,用last、current、next表示前、当前、后三个点,算出当前now点的切线方向(黄色),切线方向乘以复数i,逆时针旋转90就为法线方向,乘以复数-i,顺时针旋转90度为负法线方向,则有了Normal和-Normal(紫色)。Normal和-Normal分别乘以宽度的一半,即可求出每个点的左右端点
将端点坐标写入WebGL顶点坐标缓冲区,另外通过索引[0, 1, 3, 1, 2, 3]来表示两个三角形的顶点序号写入WebGL索引缓冲区,调用gl.drawElements绘制出带线宽的直线。
实现
事实上,计算左右端点的操作应该发生在顶点着色器中的,而且也只能在着色器中计算,因为最终显示到屏幕上的顶点与镜头相关,上述只是简单的用了2维的情况模拟,如果在js端计算,将极大消耗性能。
组织数据
组织要传给着色器的数据:position、prevPositions、nextPositions、side
比如,三个点 p0 p1 p2,要组织数据的形式:
position = [p0, p0, p1, p1, p2, p2]
prevPositions = [p0, p0, p0, p0, p1, p1]
nextPositions= [p1, p1, p2, p2, p2, p2]
side = [1, -1, 1, -1, 1, -1]
let geometry = new THREE.BufferGeometry()
let vertices = []
let count = vertices.length
let prevPositions = new Float32Array(count * 3 * 2)
let nextPositions = new Float32Array(count * 3 * 2)
let side = new Float32Array(count * 2)
let position = new Float32Array(count * 3 * 2)
let indexes = new Uint16Array(6 * (count - 1))
for (let j = 0; j < count * 2; j += 2) {
// side
side[j] = 1
side[j + 1] = -1
// index
let current = vertices[j / 2]
let prev = vertices[(j === 0 ? j : j - 2) / 2]
let next = vertices[(j === (count - 1) * 2 ? j : j + 2) / 2]
// position
position[j * 3] = current.x
position[j * 3 + 1] = current.y
position[j * 3 + 2] = current.z
position[j * 3 + 3] = current.x
position[j * 3 + 4] = current.y
position[j * 3 + 5] = current.z
// prev
prevPositions[j * 3] = prev.x
prevPositions[j * 3 + 1] = prev.y
prevPositions[j * 3 + 2] = prev.z
prevPositions[j * 3 + 3] = prev.x
prevPositions[j * 3 + 4] = prev.y
prevPositions[j * 3 + 5] = prev.z
// next
nextPositions[j * 3] = next.x
nextPositions[j * 3 + 1] = next.y
nextPositions[j * 3 + 2] = next.z
nextPositions[j * 3 + 3] = next.x
nextPositions[j * 3 + 4] = next.y
nextPositions[j * 3 + 5] = next.z
}
for (let i = 0; i < count * 2 - 2; i += 2) {
indexes[6 * (i - i / 2)] = i
indexes[6 * (i - i / 2) + 1] = i + 1
indexes[6 * (i - i / 2) + 2] = i + 2
indexes[6 * (i - i / 2) + 3] = i + 2
indexes[6 * (i - i / 2) + 4] = i + 1
indexes[6 * (i - i / 2) + 5] = i + 3
}
geometry.setAttribute('position', new THREE.BufferAttribute(position, 3))
geometry.setAttribute('prevPositions', new THREE.BufferAttribute(prevPositions, 3))
geometry.setAttribute('nextPositions', new THREE.BufferAttribute(nextPositions, 3))
geometry.setAttribute('side', new THREE.BufferAttribute(side, 1))
geometry.index = new THREE.BufferAttribute(indexes, 1)
顶点着色器计算端点坐标
正如上述 计算左右端点方法 两个点为单位轮回;算出法向量乘以side,得到最终的正负法线方向
需要注意的是,为了能够计算顶点在屏幕上的最终位置,需要把canvans的尺寸大小传递给着色器
<script id="vertexShader" type="x-shader/x-vertex">
attribute float side;
attribute vec3 prevPositions;
attribute vec3 nextPositions;
uniform float width;
uniform vec2 resolution;
void main(){
float aspect = resolution.x / resolution.y;
mat4 pvm = projectionMatrix * modelViewMatrix;
vec4 currentV4 = pvm * vec4(position, 1.0);
vec4 prevV4 = pvm * vec4(prevPositions, 1.0);
vec4 nextV4 = pvm * vec4(nextPositions, 1.0);
vec2 currentV2 = currentV4.xy / currentV4.w;
vec2 prevV2 = prevV4.xy / prevV4.w;
vec2 nextV2 = nextV4.xy / nextV4.w;
vec2 dir1 = normalize(nextV2 - currentV2);
vec2 dir2 = normalize(currentV2 - prevV2);
vec2 dir = normalize(dir1 + dir2);
vec2 normal = vec2( -dir.y, dir.x );
normal.x /= aspect;
normal *= width;
normal /= resolution.y;
currentV4.xy += (normal * side) * currentV4.w;
gl_Position = currentV4;
}
</script>
效果
export default [
{x: 0.2980022405076852, y: 0.0007317477689525731, z: 1},
{x: 1.1869354597466781, y: 0.0009180096778322877, z: 0},
{x: 2.0758235116605874, y: 0.0012041573064607292, z: 1},
{x: 2.9647108701853995, y: 0.0016485209298480186, z: 1},
{x: 3.8535979009456014, y: 0.002361701064614863, z: 0},
{x: 4.7424853104305384, y: 0.00351269568443513, z: 0},
{x: 5.631373663413683, y: 0.005294665597602943, z: 1},
{x: 6.520261987572894, y: 0.007922778765419025, z: 0},
{x: 7.409147752210174, y: 0.01167311915872915, z: 2},
{x: 8.298025451981857, y: 0.016846610763536773, z: 0},
{x: 9.186886934688118, y: 0.023772568206652522, z: -1},
{x: 10.07571907490967, y: 0.0328001040410868, z: 0},
{x: 10.964496398756296, y: 0.044288140005107834, z: 5},
{x: 11.85319602294021, y: 0.05862768318564804, z: 10},
{x: 12.741786244815671, y: 0.07613330348408454, z: 0},
{x: 13.630226377381064, y: 0.09712025624207854, z: 20},
{x: 14.518501144938796, y: 0.12186988355398398, z: 1},
{x: 15.406618008779674, y: 0.15055661742513848, z: 0},
{x: 16.294597333615457, y: 0.18321126049687564, z: 1},
{x: 17.182483414117996, y: 0.21982671919579388, z: 0},
{x: 18.07027996070667, y: 0.26025710620086784, z: 0},
{x: 18.957985046609565, y: 0.3042638844103749, z: 2},
{x: 19.845588805245143, y: 0.35157055005709026, z: 20},
{x: 20.733061996025413, y: 0.40187981369570025, z: 12},
{x: 21.620398578083382, y: 0.4548763144693453, z: 0},
{x: 22.50759421058183, y: 0.510277754478011, z: 0},
{x: 23.394649626125783, y: 0.5678463420269395, z: 10},
{x: 24.28156938481652, y: 0.6273966818090457, z: 10},
{x: 25.16835632444645, y: 0.6887999401383809, z: 0},
{x: 26.055014124105355, y: 0.7520044141307949, z: 2},
{x: 26.94154278405199, y: 0.8170242843056599, z: 0},
{x: 27.827935344684192, y: 0.8839563938809647, z: 2},
{x: 28.71418144341419, y: 0.9530044273309386, z: 4},
{x: 29.60025448188958, y: 1.0244047577025412, z: 0},
{x: 30.486116503444237, y: 1.0984849046006389, z: 50},
{x: 31.371726404611536, y: 1.175603041504246, z: 6},
{x: 32.25701896209273, y: 1.2562969882843618, z: 4},
{x: 33.14193521146046, y: 1.3409977805800963, z: 10},
{x: 34.0263999228323, y: 1.4302624318554535, z: 0},
{x: 34.91033330314474, y: 1.5245805770430252, z: 0},
{x: 35.79361266847275, y: 1.6247205249796934, z: 0},
{x: 36.676130186271166, y: 1.7312884009689355, z: 0},
{x: 37.557760528031736, y: 1.844910698533738, z: 2},
{x: 38.43832338116283, y: 1.9663458748508447, z: 0},
{x: 39.31764605054343, y: 2.0958906489368587, z: 0},
{x: 40.19564230123137, y: 2.234175946683024, z: 0},
{x: 41.07190059501124, y: 2.3830193460015607, z: 10},
{x: 41.946324364051975, y: 2.5416768732814035, z: -20},
{x: 42.81851559265749, y: 2.712179223072269, z: 0},
{x: 43.688235247276, y: 2.8943826198482725, z: -10},
{x: 44.55509274214762, y: 3.089389858893753, z: -10},
{x: 45.41866612330193, y: 3.298229478551491, z: -20}
]