BufferGeometry.boundingBox的应用:BoxHelper的实现

本文探讨了如何利用BufferGeometry.boundingBox创建BoxHelper,详细解释了BoxHelper的内部工作原理,强调不应在动画循环中频繁更新BoxHelper以避免性能影响。建议在对象变换后单独调用BoxHelper.update()方法。同时推荐使用Object3D.updateMatrixWorld()配合LineBasicMaterial动态更新线框。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

BufferGeometry.boundingBox的应用:BoxHelper的实现

通过boundingBox实现的BoxHelper

官方提供了一个BoxHelper,查看源码,它是通过boundingBox实现的。

BoxHelper通过LineSegment画出一个长方体外框,即需要通过BufferAttributes指定要画的顶点属性(这里主要是position以及index),通过创建LineSegment对象进行绘制,BoxHelper继承了LineSegemnt

// BoxHelper.js
// class BoxHelper extends LineSegment
constructor( object, color = 0xffff00 ) {const indices = new Uint16Array( [ 0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 0, 4, 1, 5, 2, 6, 3, 7 ] );const positions = new Float32Array( 8 * 3 );const geometry = new BufferGeometry();geometry.setIndex( new BufferAttribute( indices, 1 ) );geometry.setAttribute( 'position', new BufferAttribute( positions, 3 ) );super( geometry, new LineBasicMaterial( { color: color, toneMapped: false } ) );this.object = object;this.type = 'BoxHelper';this.matrixAutoUpdate = false;this.update();
} 

在构造器中,并没有给出position的据其位置,这一部分的实现在update方法,就是,在update中,间接的使用了boundingBox

下面看一下update()方法:

// BoxHelper.js
update( object ) { // 这里就是要确保调用update方法时,不会传入参数if ( object !== undefined ) {console.warn( 'THREE.BoxHelper: .update() has no longer arguments.' );}// 通过Box3.setFromObject()获取Box的坐标if ( this.object !== undefined ) {_box.setFromObject( this.object );}// 坐标获取成功if ( _box.isEmpty() ) return;const min = _box.min;const max = _box.max;// 设置position/*5____41/___0/|| 6__|_72/___3/0: max.x, max.y, max.z1: min.x, max.y, max.z2: min.x, min.y, max.z3: max.x, min.y, max.z4: max.x, max.y, min.z5: min.x, max.y, min.z6: min.x, min.y, min.z7: max.x, min.y, min.z*/const position = this.geometry.attributes.position;const array = position.array;array[ 0 ] = max.x; array[ 1 ] = max.y; array[ 2 ] = max.z;array[ 3 ] = min.x; array[ 4 ] = max.y; array[ 5 ] = max.z;array[ 6 ] = min.x; array[ 7 ] = min.y; array[ 8 ] = max.z;array[ 9 ] = max.x; array[ 10 ] = min.y; array[ 11 ] = max.z;array[ 12 ] = max.x; array[ 13 ] = max.y; array[ 14 ] = min.z;array[ 15 ] = min.x; array[ 16 ] = max.y; array[ 17 ] = min.z;array[ 18 ] = min.x; array[ 19 ] = min.y; array[ 20 ] = min.z;array[ 21 ] = max.x; array[ 22 ] = min.y; array[ 23 ] = min.z;// 因为BufferAttribute更新了,所以要重新向GPU发送数据// 通过设置BufferAttribute.needsUpdate,重新向GPU发送数据position.needsUpdate = true;this.geometry.computeBoundingSphere();
} 

update()中,最重要的就是通过获取Box3.setFromObject()获取Box的坐标以及设置BufferAttribute.needsUpdatetrue,从而向GPU发送更新后的数据。

接下来看一下Box3.setFromObject()的源码:

setFromObject( object, precise = false ) {this.makeEmpty();return this.expandByObject( object, precise );
} 

makeEmpty()就是将Box3对象恢复成默认值。

makeEmpty() {this.min.x = this.min.y = this.min.z = + Infinity;this.max.x = this.max.y = this.max.z = - Infinity;return this;

} 

接下来才是setFromObject()方法的重点,调用Box3.expandByObject()方法:

expandByObject( object, precise = false ) {// Computes the world-axis-aligned bounding box of an object (including its children),// accounting for both the object's, and children's, world transformsobject.updateWorldMatrix( false, false );const geometry = object.geometry;if ( geometry !== undefined ) {if ( precise && geometry.attributes != undefined && geometry.attributes.position !== undefined ) {// 这里调用的时候传入的为false,所以就不看这部分代码了// 其实这里是通过更复杂的计算,让结果更精确} else {if ( geometry.boundingBox === null ) {// 计算对象自己的boundingBoxgeometry.computeBoundingBox();}// 对这个boundingBox变换到世界空间_box.copy( geometry.boundingBox );_box.applyMatrix4( object.matrixWorld );// 把结果“联合”// 在后续还要递归的将子对象的boundingBox整合进来// 其实就是取了对象自己和其后代对象的所有boundingBox的最大的max和最小的minthis.union( _box );}}// 对象本身的boundingBox已经计算完毕,还要计算并整合其后代对象const children = object.children;for ( let i = 0, l = children.length; i < l; i ++ ) {this.expandByObject( children[ i ], precise );}return this;
} 

这个方法就是将一个对象及其后代对象的所有boundingBox都整合为一个boundingBox,这样,就能“包裹”住所有的对象。

通过阅读代码也可以看到,最终返回的this是被转换到了世界空间中,所以,在使用BoxHelper时,BoxHelper的实例是添加到scene而不是对象自身的局部空间。放在局部空间中的BoxHelper,首先,它的位置不对;其次,也很难保证它的边框是和世界空间的坐标轴对齐。

不要再动画循环中更新BoxHelper

BoxHelper这个对象很不同,他每次更新都会向GPU重新发送数据。一般其他的对象都是向GPU发送一次数据,后续的变换都是向GPU发送一个变换矩阵,在OpenGL的顶点着色器中,完成坐标变换。而BoxHelper则是在使用Box3完成坐标变换,然后把变换后的顶点发送给GPU。

就像BufferGeometry中对translate的描述:

Translate the geometry. This is typically done as a one time operation, and not during a loop. Use Object3D.position for typical real-time mesh translation.

这样直接更新BufferAttribute(即,重新向GPU发送数据的方法),因该是“一次性”的操作,不因该把update放在动画循环中。BoxHelper应该仅在换其他对象时,调用setFromObject()方法,从而触发update()

如果想要实现一个想要跟随对象变换的线框(即,在动画循环要更新线框),应该是使用WireframeGeometry配合LineSegment

最后

整理了一套《前端大厂面试宝典》,包含了HTML、CSS、JavaScript、HTTP、TCP协议、浏览器、VUE、React、数据结构和算法,一共201道面试题,并对每个问题作出了回答和解析。

有需要的小伙伴,可以点击文末卡片领取这份文档,无偿分享

部分文档展示:



文章篇幅有限,后面的内容就不一一展示了

有需要的小伙伴,可以点下方卡片免费领取

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值