当我一个实例化网格模型里面的小实例们相交时,他们本身都是同一种材质,在都是同一种颜色时没有问题,而在我点击更改了其中一个的颜色后,就会出现闪烁问题,这种现象的本质还是Z-Fighting。
在之前关于Z-Fighting的文章中(three.js 模型重合相交部分闪烁 Z-Fighting)我已经说了几种解决闪烁的方法,但是面对实例化网格模型用的是一个材质时,我们的多边形偏移就没法奏效了。
于是我想到一个曲线救国的方式:
1. 在点击之后,把这个小实例隐藏;
那么怎么隐藏实例呢,我们可以使用setMatrixAt函数把该实例的矩阵设置为零矩阵,可以把这次的实例index记录下来,方便下次还原
//隐藏实例
var zeroMatrix = new THREE.Matrix4();
zeroMatrix.set(
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0
)
mesh.setMatrixAt(index, zeroMatrix);//mesh就是要处理的实例化模型InstancedMesh,index就是实例索引
mesh.instanceMatrix.needsUpdate = true;
2. 重新创建一个新的临时网格模型chooseFakeMesh,代替这个实例,材质使用多边形偏移,并且偏移量设置要在原本InstancedMesh的上层;
2.1 创建一个新的临时网格模型chooseFakeMesh ,使用选中材质;
这里我选中材质的polygonOffsetUnits和polygonOffsetFactor都是负的,且polygonOffsetUnits设置的很小,具体应用的时候只要设置的比其他InstancedMesh的材质偏移量小就可以了。(偏移量越小,深度越小,离镜头越近,在上层)
//选中材质
var chooseMaterial = new THREE.MeshLambertMaterial({
color: this.chooseColor,
polygonOffset: true,
polygonOffsetFactor: -1,
polygonOffsetUnits: -100,
});
//创建一个选中材质的Mesh
this.chooseFakeMesh = new THREE.Mesh(new THREE.BoxBufferGeometry(1, 1, 1), chooseMaterial);//初始化使用1*1*1的立方体,方便之后调整大小
2.2 改变大小和位置,代替这个实例
var item = mesh.baseInfoArr[index];//我在原先的InstancedMesh中用baseInfoArr记录了实例的相关信息
var boxinfo = item.boxinfo;//这里面存放的是原来那个实例的相关位置和大小信息
this.chooseFakeMesh.position.set(boxinfo.x, boxinfo.y, boxinfo.z);//坐标
this.chooseFakeMesh.scale.set(boxinfo.width, boxinfo.height, boxinfo.depth);//放大到选中的实例大小
this.chooseFakeMesh.visible = true;
//再将这个chooseFakeMesh放到场景中就行了
2.3 将这个chooseFakeMesh放到场景中
//加入实例化模型的父级里面,与实例化模型同级
//isFake: true方便之后可以通过这个标识找到它,可以添加你想要的属性
mesh.parent.add(Object.assign(this.chooseFakeMesh, { isFake: true }));
//加入可点击模型的数组中
//this.clickObjects这个是我存储可点击模型的数组,也就是传给raycaster.intersectObjects()的参数
this.clickObjects.push(this.chooseFakeMesh);
3. 之后在需要的时候,再把这个小实例还原回来;
还原矩阵这里可以和我们初始化时一样相同通过设置矩阵的坐标和放大倍数来做,我是直接设置矩阵的结果,使用matrix4.set()
matrix.set(
boxinfo.width, 0 , 0 , boxinfo.x,
0 ,boxinfo.height, 0 , boxinfo.y,
0 , 0 , boxinfo.depth , boxinfo.z,
0 , 0 , 0 , 1
);
width/height/depth分别表示实例的X轴上面的宽度,Y轴上面的高度,Z轴上面的深度,对应矩阵的缩放倍数
x/y/z表示实例的坐标,对应的也是矩阵的坐标
得到的矩阵:与传入的矩阵是转置关系(注意x\y\z的位置)
matrix.elements = [ boxinfo.width, 0 , 0 , 0,
0 , boxinfo.height, 0 , 0,
0 , 0 , boxinfo.depth , 0,
boxinfo.x , boxinfo.y , boxinfo.z , 1 ]
完整还原实例的代码:
//还原上次构件实例
//mesh:实例化模型InstanceMesh,必需
//chooseindex:上次选中的index,可选
restoreMesh(mesh, chooseindex) {
var matrix = new THREE.Matrix4();
if (chooseindex >= 0) {
var item = mesh.baseInfoArr[chooseindex];//这个是我存储实例本来的相关信息的,可以替换成你记录信息的变量
var boxinfo = item.boxinfo;//实例的立方体相关变量,长宽高、三维坐标
matrix.set(
boxinfo.width, 0, 0, boxinfo.x,
0, boxinfo.height, 0, boxinfo.y,
0, 0, boxinfo.depth, boxinfo.z,
0, 0, 0, 1
);
mesh.setMatrixAt(chooseindex, matrix);
mesh.instanceMatrix.needsUpdate = true;
} else {
//如果没有传指定的index,则还原实例化网格模型中的所有实例
_.times(mesh.count, index => {
var item = mesh.baseInfoArr[index];
var boxinfo = item.boxinfo;
matrix.set(
boxinfo.width, 0, 0, boxinfo.x,
0, boxinfo.height, 0, boxinfo.y,
0, 0, boxinfo.depth, boxinfo.z,
0, 0, 0, 1
);
mesh.setMatrixAt(chooseindex, matrix);
})
mesh.instanceMatrix.needsUpdate = true;
}
}
4. 临时模型chooseFakeMesh处理
可以再次变换为新点击的实例(也就是重复2.2)
也可以根据情况隐藏,需要的时候要放出来使用:
//隐藏模型
this.chooseFakeMesh.visible = false;
//如果有父级,在父级里面移除掉模型
this.chooseFakeMesh.parent ? this.chooseFakeMesh.parent.remove(this.chooseFakeMesh) : null;
//如果把它加入进点击能够点到的数组,也要记得移除
_.remove(this.clickObjects, { 'isFake': true });