Three.js通过点击改变一个面的材质

之前做了一个题目是基于WebGL的家居设计,为此专门去学习了Three.js,过程中遇到了很多问题坎坎坷坷最后也都解决了,其中有一个捣鼓了很久的情况是这样的:

有一面空白的墙(就是一个立方体geometry),实现用鼠标点击这面墙的一面就能为其添加选中的墙纸。效果如下:

Three.js通过点击改变一个面的材质
图1
Three.js通过点击改变一个面的材质
图2

刚刚开始的时候并没有搞懂的一个概念就是face。导致走了很多弯路。简单来说,在Threejs中,一个普通的方形面由两个三角形组成,其中一个三角形就是一个face。如下面的1和2

Three.js通过点击改变一个面的材质
图3

更复杂的面由更多的三角形(face)组成,比如上面抠去了一个窗的墙。

Three.js通过点击改变一个面的材质
图4

要实现鼠标点击改变墙纸,在Threejs中要通过鼠标点击实现的效果,那基本少不了raycaster.intersectObjects(XXX,false)intersectObjects函数返回射线raycaster从起点向终点发出的射线碰到的所有XXX的子类组成的数组。第二个参数默认为false,改为true还可以遍历子类的子类,关于raycaster的使用网上有很多专门说明的文章,这里就不赘述了。

raycaster.setFromCamera( mouse, camera3D );

var Choosed = raycaster.intersectObjects( XX , false );

我们需要的只是数组中的第一个对象Choosed[0],射线碰到的第一个物体,也是从我们的视角看,鼠标点到的第一个物体。

打印一下Choosed[0]能看到

Three.js通过点击改变一个面的材质
图5

其中的face就是指鼠标点击到的物体(下面的object)的face。face下的属性abc是这个面的法向量,下面的normal是(a,b,c)的归一化(单位化)。标注出来的materialIndex就是对应object中的material的序号。要注意的是,对于一个原始的object,它的material并不是数组形式(图7)。

Three.js通过点击改变一个面的材质
图6
这个object添加了额外的material,类型是Array
Three.js通过点击改变一个面的材质
图7
这个object没有添加额外的material
类型是Object

要在material中保存多个材质图,需要进行如下的操作。

假设我们想要object同时存储 material1 和 material2

var material01 = new THREE.MeshBasicMaterial({map:texture01});
var material02 = new THREE.MeshBasicMaterial({map:texture02});
var materials = [];

materials.push(material01);
materials.push(material02);
Choosed[0].object.material = materials;//这样material就能存储多个材质

之后改变图5中 face的materialIndex即可改变其对应的material

而要改变一面整墙的材质,需要遍历faces,找出其中和 facenormal相同的face 并改变其materialIndex

PS:因为精度的问题,一面墙上的两个face可能朝着同一面但两者的normal会有偏差。例如图3的1和2法向量分别为(1,1,1)和(1,1.00000001,1),这种情况写个函数精确到某一位再来比较就行了。

在改变后需要将geometry的groupsNeedUpdate属性设置为true来告知渲染器该对象需要更新

Choosed[0].object.geometry.groupsNeedUpdate = true;

当初在这里卡了大概有一周左右,毕竟是刚刚开始接触Threejs,网上百度相关的资料来来回回就是那几篇,很多概念没搞懂就冲上去瞎搞。(。_ 。)在最后从Threejs的doc里面一条一条的看,偶然看到了face的那一条才恍然大悟。虽然大悟后弯路也没少走就是了......

参考资料: https://threejs.org/docs