WebGL2画球

本文演示的代码均在这里,本文仅说明原理,不会细化到webgl基础。

最近学会了用WebGL2画球,其实学会也有两个多月了,但由于某些误解一直没法生成正确的贴图映射,导致贴图错乱,直到两周前才突然开窍发现了原因,又拖到今天才来发这么一篇文章。忙里偷闲做事就是效率低下,好在并没有什么迫切需求要做,慢慢学也无所谓。

要用WebGL画球主要就是要先建个球模,然后给它上色或者贴图。要贴图就要搞清楚每个顶点和贴图的坐标映射关系,不同的球模类型映射关系也不一样,我这画的是最容易理解的经纬球,即以地球仪那种经纬线为线框的球体。

接下来开始我们的正题。

先摆一个随便找的经纬球

现在假装看不见经线(竖线),只能看见纬线(横线),那么这个球外面每隔一定的角度就绕着一个正圆,我们生成模型也可以用这种逻辑,首先生成一个正圆,然后把它按角度调整好大小并放到正确的位置上。关于生成一个合适边数的圆的方法我在前一篇文章已经写过了,这里就不再赘述了。

要画出一个球,首先确定每个点的坐标,然后确定顶点的绘制方法和顺序。

由于WebGL就是封装了个OpenGL,所以以下就简称成gl了,因为概念都是通用的。

顶点篇

首先要确定顶点的位置,虽说是把圆环放到正确的位置上,但实际上我们并不会画出这个环,而是要用到这个环的顶点坐标。

为了便于理解,这里还是用画环来举例。假设南北极轴为z轴,圆半径是1,圆心为原点在xy平面上画圆,圆环顶点的xy坐标就可以通过经线所在的角度(rad)算出来,为了直观地把贴图贴在球上,这里以逆时针绘制这个环(计算每个点增加的角度是负数),那么[x,y]=[r*cos(rad),r*sin(rad)],因r=1,故[x,y]=[cos(rad),sin(rad)]。

图1

一个圆环画好之后就是画多个圆环了,我们暂时可以不管各个圆的大小与位置问题,仅把它们平均分布到z轴平面上,最后就会得到一个圆环桶(该图由geogebra 3d绘制,并非最终效果)。

图2

到目前为止,都是在讲如何确定一个立体的圆环柱各个顶点的坐标,为了方便讲解贴图,这里暂时不把它变成一个球,要让它变成一个球只需要在最后改动一下决定顶点坐标的代码就行了。

把上面的圆桶沿虚线剪开,摊平,现在来讲讲这个圆环桶的顶点绘制顺序,请看下图。为了画起来方便,下图减少了顶点数,

这是一个有4条经线,5条纬线(含极点)的“圆柱”展开图,该图左右两边是完全重合的,如同在2d画布上连接一个圆的顶点后要把最后一个点和第一个点连起来以闭合图形一样,3d模型也需要这种额外的顶点来封闭模型,否则这个圆柱上会出现一道缝。下图的①所在列为所有圆环的起点,⑦所在列为所有圆环的最后一个顶点,而⑨所在列为额外增加的和起点重合的点,这个圆柱中的圆环都有4条经线,5个实际顶点。图中虚线表示在绘制路径中重用的点,在下面会另外讲到,这里不必在意。

图3

这种顶点顺序适合使用`TRIANGLE_STRIP` 和`LINE_STRIP` 这两种条带绘制模式。`TRIANGLE_STRIP`三角形条带绘制模式下,gl会将相邻的3个点坐标作绘制为一个三角形。由于GL默认在画面上逆时针绘制出来的三角形为“正面”,顺时针画出来的为“背面”,因此我们的连线顺序是先向下再向右上的。又因为条带绘制模式下只要确保第一个三角形是正面的,那么后面连着的三角形就都会跟随第一个三角形的朝向,就像被连着贴在了一条胶带上一样,当然如果这个胶带的一部分被扭曲成了反面朝向我们,那么那些位置上的三角形就是处于反面绘制的状态。下图表示一个三角形条带的绘制顶点顺序,图中黄色的是顶点顺序,红色的是三角形的顺序。

图4

线条条带`LINE_STRIP`则是把上面的顶点按照索引顺序都用直线连起来,不会填充三角形颜色。

我们把上面示例网格的每一层三角形条带按绘制顺序全部展开来的话那么看起来就是这样的。注意,⑨⑩和⑪三个点虽然看起来在一条直线上,但它们在球面上时并不在一条直线上,会有一定的角度,所以顶点⑩的索引要重复两次,这样就可以变成⑨⑩⑩这样的无效图形,而不会在球里面冒出一个三角形来。关于索引,在后面说明。

图5

说了那么多,是时候上个实例了(可用鼠标拖动),为了看起来不眼花,我给不同位置的线涂上了不同的颜色。第一个是用`LINE_STRIP` 画出来的,第二个是用`TRIANGLE_STRIP` 画出来的。


这就是前面的网格绕圈卷起来的效果

索引篇

上面提到了顶点索引,这个概念是用来告诉gl画某个点的时候要用到顶点数组里的哪一个点,这里开始要配合一点代码来说明了。

顶点数组这里就不多说了,毕竟不是基础webgl教学,大致上讲就是将图2中的每一个圆环的顶点xyz坐标按逆时针顺序放进一个类型数组里,通常是Float32Array。如果你此时产生了疑问,为什么不需要按照前面说的顶点顺序来放进顶点数组呢?那么你就确实需要看一看这一篇的内容。

如果按照前面所说的顺序把顶点坐标的三个数字放进顶点数组的话,假如不用条带模式来绘制,而用单独的三角形绘制模式,那么一个顶点被几个三角形用到就要重复几次,上例中最多需要往顶点数组里放6次,只算xyz坐标的话就需要用掉3*6=18个数组空间,条带模式下上例绘制一个顶点最多需要重复4次放进顶点数组,即2*4=8个空间,但还是太多了。因此我们需要一个方法,可以把所有顶点不重复地放在一个顶点数组里,然后用另一个数组来告诉gl画某一个点的时候去找顶点数组里的第几个点的坐标。

比如现在有一个顶点数组Float32Array,每一行是一个点的三轴坐标

[
1,0,0,
1,1,0,
1,1,1,
2,0,0,
2,1,0,
2,1,1,
]

再准备一个索引数组Uint8Array,每一行是一个三角形的三个顶点

[
0,1,2,
1,2,3,
2,3,4,
3,4,5,
0,3,4,
2,4,5,
2,3,5,
0,3,5,
]

则该索引第一行表示指定一个三角形的三个顶点为顶点数组中的第0、1、2组顶点坐标,即

[
1,0,0,
1,1,0,
1,1,1,
]

第5行表示三个顶点是顶点数组中的第0、3、4组坐标,即

[
1,0,0,
2,0,0,
2,1,0,
]

如果不使用顶点索引的话,那么就需要24*3*3=216个Float32Array空间,数据为864Byte,而使用索引则需要3*6=18个Float32Array空间和3*8=24个Uint8Array空间,数据为264Byte。顶点重用度越高,则节省的内存就越多。

注意:如果模型的顶点数非常多,则需要考虑到底该使用Uint8Array还是Uint16Array甚至Uint32Array,如果索引超过了该数据类型上限,会出现一些奇怪的现象。

顶点索引的使用方法也很简单,只要先创建一个数据缓冲区绑定,然后在绑定和使用bufferData填充数据的时候把第一个参数设置成`ELEMENT_ARRAY_BUFFER` 就表示传入的是顶点索引数据了

let gl=获取gl对象;
let buffer=gl.createBuffer();//创建缓冲区
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER,buffer);//绑定缓冲区
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER,索引数组,usage参数);

然后使用drawElements绘制该模型即可。以上函数随便网上查查都有各种各样的用例,在此就不赘述用法了。

贴图篇

向一个球面贴图是一件很容易让初学者蒙圈的事情。大家学习贴图的时候可能看到的都是给一个正方形或者立方体贴图,那很容易理解,如果还不知道纹理坐标是什么的话建议先去看更基础的纹理教程,否则很难理解接下来的内容。

给图形贴图,本质上还是给三角形贴图,简单地讲就是告诉gl这个三角形的每一个顶点对应的是一张图片上的哪一个点,然后gl会帮你把图片蒙到这个三角形上。那么如何给一个球贴图呢,下面放一张图你应该就能明白了(注意,这种经纬球由于两端顶点合并后两端的三角形将有一半无法绘制,所以两端的贴图会有撕裂情况,需要将网格精细度调高来弱化这种问题)

图6

贴图文件在这:e.jpg。如果你明白了的话可以直接跳到下一篇了,如果还没明白的话,就继续看下去。

最重要的一点是纹理映射的坐标是和存放在顶点坐标数组中的顶点对应的,而不是和顶点索引相关的,我就是被因此导致的贴图错误折腾了好久。因为和顶点对应,所以放入纹理坐标数组的纹理坐标也是按照每个圆环逆时针顺序顶点对应放的。

纹理xy坐标取值范围都是0到1,0表示上边或最左边,1表示最右边或最下边。那么上面①的纹理坐标就是[0,0],②的纹理坐标是[1/4,0],⑧的纹理坐标是[2/4,1/4]。

贴好以后就会是这样的效果:

是时候把我们的圆柱改成球体了

先放一张示意图

 

图7

这是一张侧视图,图中AB为我们要计算的圆环,假如B就是其中一个顶点。图中可以看出该圆环明显和球直径上的圆大小不一样,所以我们可以对其xy坐标进行缩放,为了让所有的圆环都能使用一套缩放公式,故使用∠DCB进行计算。这个缩放很简单,一个三角函数即可算出,缩放scale=sin(rad)/球半径r,因为半径=1,所以scale=sin(rad),接下来让该圆环的所有顶点xy坐标都乘以scale就行了。然后是z轴坐标,也可以通过一个三角函数算出z=r*cos(rad),因为r=1故z=cos(rad)。

下面特别放一下圆柱和球体的顶点坐标区别。其它代码由于示例可能右键不会出来查看源码的选项,请看此文件,几乎每一行我都写了注释。由于有些webgl方法都是有固定的使用组合的,因此我把一些组合打包进了glRoom.js中,使代码看起来简洁一点。

if(pageArgs.modelCylinder){//生成圆柱的顶点
	vertexArr.push(
		Math.sin(rad),//x
		Math.cos(rad),//y
		1-circleInd/circleCount*2,//z
	);
}else{//生成球的顶点
	vertexArr.push(
		scale*Math.sin(rad),//x
		scale*Math.cos(rad),//y
		Math.cos(circleAddRad*circleInd),//z
	);
}

到这里我们已经把原来的圆柱换成了球体,这篇文章的内容也就正式结束了,最后摆上成品示例。

成品球

`LINE_STRIP`模式的球

没有贴图的彩球

`LINE_STRIP`模式的彩球

闲聊篇

第一次用到webgl是在做弹幕播放器的时候,为了性能我做了css、canvas2d和webgl三种模式的弹幕,并且让这三种模式可以无缝切换而且看起来基本无异,但最后测试下来由于当时并不是很了解webgl,做出来的webgl弹幕模式性能奇差,而且到现在我也没去优化它,而是直接把它禁用了。这次突然开始学webgl2是为了更新以前的音频可视化作品,以前都是用canvas2d绘制的,现在想提升一个维度来表现可视化效果了。

如果你是想了解在webgl中绘制球形的方法而来看这篇文章却没看懂的话,欢迎留言,我会继续修改文章来让它更加易懂。



本文发布于 https://luojia.me

本站文章未经文下加注授权不得拷贝发布。

0 0 投票数
打分
订阅评论
提醒
guest
2 评论
内联反馈
查看所有评论
Handle
游客
Handle
4 年 前

看不懂(doge

晨旭
游客
4 年 前

看不懂(doge