需求背景
需要在3d模型上实现标注的功能,一开始是直接通过添加一个普通的mesh来实现的,但是这样就会有一个问题-当视图缩放的时候,标注也会跟着一起放大缩小,影响视觉效果,因此需要实现一个不会随着视图一起放大或者缩小的mesh
实现思路
明确方向
- 根据需求,可以知道我们其实需要实现的就是更改模型渲染的默认方式,而模型的渲染是由模型的
MVP
三个矩阵来决定的 - 再进一步分析
MVP
三个矩阵,Model
矩阵决定模型的旋转、缩放和位移,View
决定相机的变换,Projection
决定模型最终到屏幕的输出。根据需求,我们只需要修改Model矩阵当中的缩放就可以了。
具体实现
- 查看threejs的源码可知,
scene
中的Mesh
都是继承自Object3D
这个基类的,其中的matrixWorld
属性就对应着我们上面提到的Model
矩阵,因此只要我们能正确的更改这个矩阵应该就能实现我们想要的效果了。 - 那么如何更改这个
matrixWorld
呢?那就是updateMatrixWorld
这个方法了。我们只需要在这个方法里面提取出Model
矩阵中的缩放因子并去除就可以了 - 因为涉及到了更改
Object3D
源码,因此我们可以实现一个新的类Mark
,让它继承自Object3D
,再改写其中的一些方法就可以了 - 代码—
import { Mesh, Object3D, Quaternion, Vector3 } from 'three';
class Mark extends Object3D {
constructor(camera, position) {
super();
this.type = 'Mark';
this.camera = camera;
this._position = position || new Vector3();
this.size = 1;
this.worldPosition = new Vector3();
this._worldQuaternion = new Quaternion();
this._worldScale = new Vector3();
this.cameraPosition = new Vector3();
this._cameraQuaternion = new Quaternion();
this._cameraScale = new Vector3();
this.add(this.setupBall())
}
setupBall() {
const group = new THREE.Group();
const bar = new Mesh(
new THREE.CylinderGeometry(0.1, 0, 2, 20, 4),
new THREE.MeshBasicMaterial({color: 'red'})
)
bar.position.setY(1)
const ball = new Mesh(
new THREE.SphereBufferGeometry(0.6, 20, 20),
new THREE.MeshBasicMaterial({color: 'red'})
)
ball.position.setY(2)
group.add(bar)
group.add(ball)
return group
}
// override `updateMatrixWorld`
updateMatrixWorld( force ) {
this.matrixWorld.decompose( this.worldPosition, this._worldQuaternion, this._worldScale );
this.camera.updateMatrixWorld();
this.camera.matrixWorld.decompose( this.cameraPosition, this._cameraQuaternion, this._cameraScale );
const factor = this.worldPosition.distanceTo( this.cameraPosition ) * Math.min( 1.9 * Math.tan( Math.PI * this.camera.fov / 360 ) / this.camera.zoom, 10 );
this.scale.set( 1, 1, 1 ).multiplyScalar( factor * this.size / 40);
super.updateMatrixWorld( this );
}
}
Mark.prototype.isMark = true;
export { Mark };
- 使用
// 使用
function addMark(direction, position, parent) {
const axis = new THREE.Vector3(0, 1, 0);
const mark = new Mark(camera, position);
const positionLocal = parent.worldToLocal(position);
mark.quaternion.setFromUnitVectors(axis, direction.clone().normalize());
mark.position.copy(positionLocal);
parent.add(mark);
}