【BufferGeometry】2021-r126版three.js中BufferGeometry类的使用学习
刚开始接触three.js,记录在这里免得自己忘记,欢迎萌新交流,以及跪求大佬指点!
【写在最前面提醒自己和其他萌新:常见的网课大多很老了,目前发现还是看官方文档+结合官方给出的实例源码来自己琢磨更好些!网课可作为参考,一些代码的写法可能已经不适用了。】
我的理解是,BufferGeometry是自由度最高的创建几何/几何体的方式,以前似乎有Geometry,但我在目前版本r126的官方文档(https://threejs.org/)里已经找不到了,两者的区别是,buffer版会把GPU计算后的数据存放在缓冲区后进行展示,后续修改时,可以减少GPU的计算,进而提升性能。
看一个我自己写的入门小例子,先放图,其中蓝色线条为世界坐标轴,坐标轴交点是0,0,0,threejs中右和上为x,y轴的正向,垂直屏幕向用户为z轴的正向。该图片中摄像机的位置为左侧靠上。
上图从左至右是三种不同的展示方式,分别是点,线,面。
使用过封装好的模型(例如BoxGeometry)的话,应该知道,一个模型是由几何,材质,展示方式组成的,一般这样写:
var geometry = new THREE.BoxGeometry( 1, 1, 1 );//几何
var material = new THREE.MeshBasicMaterial( {color: 0x00ff00} );//材质
var cube = new THREE.Mesh( geometry, material );//展示
scene.add( cube );//添加到场景里
BufferGeometry自定义的仅仅是几何的部分,后面都是一样的。几何需要设置的包括点,点的颜色,法向量,用点的索引表示的面片等,法向量是跟光线有关的,先跳过,现在只看点,颜色,面片。
首先new一个空的几何出来,然后创建顶点组,可以看到这里用的是Float32Array来创建的,这是因为在js里,number类型默认的是64位浮点型,太大了,所以用32位来储存。ArrayBuffer数组有好多种类,目前我看到的webGL源码基本都是用的Float32,那姑且就都用这个了吧(对了,我试了下,BufferAttribute不支持64位)。
创建一个这样的数组可以像下面代码里这样写,即:new Float32Array([]),这是把普通数组转换成Float32Array数组,一个数字占4个字节,这种写法只能传入固定已知的数组,不能var a = [];new Float32Array(a);这样是不行的。
搞懂Float32Array后,可以看到一共有8行,每行3个数字,这代表8个顶点,每一行从左至右的顺序是该点的x,y,z值,这些点的索引顺序是从上至下的,以0为开头,后面会用到这个索引。
在创建完点集后,通过setAttribute可以设置节点属性,BufferAttribute中的3意思是每个点由3个数组成。
const geometry_zdy = new THREE.BufferGeometry();
const vertices_zdy = new Float32Array( [
-8.0, 8, 40.0,//0
-38.0, 8, 40.0,//1
-8.0, 8, 0.0,//2
-38.0, 8, 0.0,//3
-8.0, 38, 0.0,//4
-38.0, 38, 0.0,//5
-8.0, 38, 40.0,//6
-38.0, 38, 40.0,//7
] );
geometry_zdy.setAttribute( 'position', new THREE.BufferAttribute( vertices_zdy, 3 ) );
接下来设置顶点的颜色,注意threejs里的颜色不能用0~255的rgb来表示,要么用16进制,要么用0-1的rgb数值。
8x3是8个点x3个rbg数的意思,(vertices_zdy[i++]+50)/100是顶点间颜色的过渡,其中除以100是为了归一化到0-1内,这个值应该是模型两个点之间,在x,y,z轴上距离的最大值,说人话就是,比如如果你画了个正方体,且宽度是100,那么就可以除以100,这是为了满足r,g,b三个值必须在0-1内的需求。
其次,显然,r,g,b三个值缓慢增加或减少,即可展示出颜色的缓慢变化,因此,用顶点的x,y,z值来辅助计算rgb(代码中vertices_zdy[i++]的用意),即可达到相近点颜色也相近,且缓慢过渡的效果(是点不是线/面)。
创建完顶点颜色集后,一样设置属性即可,这里要注意的是,由于colors_zdy是变量,所以不能用new Float32Array([]),这里改成直接在设置属性时,指明Float32BufferAttribute。
var i=0;
const colors_zdy = [];
while(i<8*3){
colors_zdy.push((vertices_zdy[i++]+50)/100);
}
geometry_zdy.setAttribute( 'color', new THREE.Float32BufferAttribute( colors_zdy, 3 ) );
然后创建面片,setIndex是通过顶点索引来指定三角形面片的,正方体一面是两个三角形,所以绘制了4个面(上文图片最右侧,只画了四面)是要8个三角形,一行一个。
【注意,如果这里不创建面片的话,则按照定点数组来绘制面,如上面代码中指示顶点为8个,从第一个点开始,每3个会被认为是一个面,所以如果注释掉setIndex这一段,可以看到只绘制了两个三角形,所以,也可以用顶点数组来绘制面。】
geometry_zdy.setIndex( [
0, 3, 1,
0, 3, 2,
4, 0, 2,
4, 0, 6,
5, 6, 4,
5, 6, 7,
1,5, 3,
1, 5, 7
] );
到这里顶点,顶点颜色,面都设置完毕了,开始绘制3种不同的展示,这里就到了材质和展示,而不是BufferGeometry的部分了。
THREE.PointsMaterial可以设置点材质,相应的,Line,Mesh是线和面,其中,vertexColors可以设置一个具体的颜色值,也可以用THREE.VertexColors来表示按照顶点颜色来绘制,此外,size是顶点的大小。
用vertexColors: THREE.VertexColors这个写法可以很方便的实现一个渐变的效果,就像图片中所示。
要特别注意的是,mesh中指定按照顶点颜色来绘制,是要用 vertexColors : true来表示的,不是vertexColors: THREE.VertexColors。
此外要注意的是,默认是单面绘制的!所以,如果保持默认,则顶点的顺序很重要,正向的顺序可以看到绘制结果,逆向就啥也木有了,因此最好在mesh的材质里声明双向绘制side: THREE.DoubleSide。
const material_zdy = new THREE.PointsMaterial( { size: 1, vertexColors: THREE.VertexColors } );
const material_zdy2 = new THREE.LineBasicMaterial({
vertexColors: THREE.VertexColors//以顶点颜色为准
});
var material_zdy3 = new THREE.MeshBasicMaterial({
vertexColors : true,
side: THREE.DoubleSide
});
最后是创建一个物体,分别是点,线,或者面,可以理解为不同的展示方式,它们使用的都是同一个几何框架,但材质不一样。之后将他们分别添加到场景中,即可看到文章中图片的效果。
const mesh_zdy = new THREE.Points( geometry_zdy, material_zdy );
const mesh_zdy2 = new THREE.Line( geometry_zdy, material_zdy2 );
const mesh_zdy3 = new THREE.Mesh(geometry_zdy, material_zdy3);
scene.add(mesh_zdy3);
scene.add(mesh_zdy2);
scene.add(mesh_zdy);
最后,我在部分教程中看到,以前似乎存在THREE.Face3来创建面片的方法,以及这些创建出的面片需要放在几何的faces数组中,但我看了下源码,在BufferGeometry的构造函数中没有找到faces,可能是已经没有了吧,没有用过之前的版本,不敢乱说,权当做提醒。