今年下半年主要负责了一个3d低代码的开发,所以借此机会分享一些此块的东西
系列篇一,共计有三篇来完成,主要来完成下面gif中的示例
应用
在3D低代码编辑器中线有哪些应用呢?我们接下来的例子为实现以下案例
- 绘制墙体
- 绘制公里
- 种植树木、草坪
知识储备
threejs线扫盲
直线
在threejs中所有的物体都是由几何体和材质组成的,线当然也不例外
线型几何体
threejs核心包中没有内置线几何体,因为本身两个点就能确定一条直线较为简单。所以在threejs中,设置线相关的顶点信息只需要给BufferGeometry借助setFromPoints设置点坐标信息即可
线型材质
threejs内置了两种线型材质
- LineBasicMaterial
- LineDashedMaterial
如名称一样,LineDashedMaterial 主要用于描绘虚线
线型网格对象
在threejs中提供了三种线相关的网格对象
- Line
- LineLoop
- LineSegments
它们的核心区别就是内部调用webgl的drawArrays APi mode参数不同
drawArrays 的7中mode
- gl.POINTS: 绘制一系列点
- gl.LINE_STRIP:绘制一个线条。即,绘制一系列线段,上一点连接下一点。
- gl.LINE_LOOP: 绘制一个线圈。即,绘制一系列线段,上一点连接下一点,并且最后一点与第一个点相连。
- gl.LINES: 绘制一系列单独线段。每两个点作为端点,线段之间不连接。
- gl.TRIANGLE_STRIP:绘制一个三角带
- gl.TRIANGLE_FAN:绘制一个三角扇
- gl.TRIANGLES: 绘制一系列三角形。每三个点作为顶点。
而这里threejs内置的三种线型集合体的核心区别是
- Line 使用的gl.LINE_STRIP
- LineLoop 使用的gl.LINE_LOOP
- LineSegments 使用的gl.LINES
创建一个简单的线
ini
const material = new THREE.LineBasicMaterial({
color: 0x0000ff
});
const points = [];
points.push( new THREE.Vector3( - 10, 0, 0 ) );
points.push( new THREE.Vector3( 0, 10, 0 ) );
points.push( new THREE.Vector3( 10, 0, 0 ) );
const geometry = new THREE.BufferGeometry().setFromPoints( points );
const line = new THREE.Line( geometry, material );
曲线
生成曲线与直线的生成略有不同。直线两点就可确定,但是对于曲线它则需要更多的点,点数越多曲线越完美(但性能也需要的更多)。
故曲线比之直线多了一个步骤,那便是生成曲线点位信息。对于曲线的生成,threejs也内置了很多方式。比如使用率最高的线条曲线和贝塞尔曲线。
贝塞尔曲线
首先来写一个三维二次贝塞尔曲线来看一下
scss
const curve = new QuadraticBezierCurve3(
new Vector3(-10, 0, 0),
new Vector3(20, 15, 0),
new Vector3(10, 0, 0),
)
const points = curve.getPoints(50);
const geometry = new BufferGeometry().setFromPoints(points)
const material = new LineBasicMaterial({
color: 0xff0000
})
const curveObject = new Line(geometry, material)
上面解释了,贝塞尔曲线更加像手绘版本,更加好看。那么我们在绘制一个样条曲线来对比一下,将控制点作为生成样条曲线的一个中间点位
scss
const curve2 = new CatmullRomCurve3(
[
new Vector3(-10, 0, 0),
new Vector3(20, 15, 0),
new Vector3(10, 0, 0),
]
)
const points = curve2.getPoints(100);
const geometry = new BufferGeometry().setFromPoints(points)
const material = new LineBasicMaterial({
color: "#ffffff"
})
const curveObject = new Line(geometry, material)
通过画面对比一下,贝塞尔曲线果真是好看不少
其他
- 二次贝塞尔与三次贝塞尔的区别,表面是看是三次贝塞尔多了一个控制点。故可知它能实现的曲线效果可以更加高级复杂。更深处可以去了解两者的曲线方程
案例一:线的绘制
首先让我们先来写一个根据鼠标点绘制直线的功能
开始先来绘制一个平面
scss
const planeGeometry = new PlaneGeometry(100, 100)
const material = new MeshBasicMaterial({ color: 0xffffff, side: 2 })
const plane = new Mesh(planeGeometry, material)
plane.rotateX(-Math.PI / 2)
scene.add(plane)
然后,我们在鼠标点击拾取到平面时获取当前坐标进行记录,并开始绘制直线
ini
const MAX_POINTS = 500;
const positions = new Float32Array(MAX_POINTS * 3);
let count = 0
const lineGeometry = new BufferGeometry()
lineGeometry.setAttribute('position', new BufferAttribute(positions, 3));
const lineMaterial = new LineBasicMaterial({ color: 0x000000 })
const line = new Line(lineGeometry, lineMaterial)
const setPoints = (point: Vector3) => {
positions[count * 3] = point.x
positions[count * 3 + 1] = point.y
positions[count * 3 + 2] = point.z
count++
line.geometry.setDrawRange(0, count);
}
点击是进行点的记录
typescript
plane.addNatureEventListener('click', (object, intersect) => {
setPoints(intersect?.point!)
})
其实这里的核心,就是线的重新渲染。有两种方式
- 将原line网格从场景中移除在重新走一遍绘制
- 更新buffer
一般为了性能考虑,最好是使用方式2。但是使用方式2有几个点需要注意一下
- 动态地调整BufferGeometry的buffer大小的操作通常会比较昂贵,因为这实际上涉及到创建一个全新的geometry,然后将旧数据复制到新的buffer中。这个过程可能会涉及内存的重新分配和大量数据的复制,这会导致性能消耗相对较高。因此,在使用BufferGeometry时,最好在创建时就预先分配足够大的buffer来容纳可能创建的任意新顶点数,而不是动态地调整buffer的大小。这样可以避免不必要的性能开销
如上代码示例我先创建了一个500个点的buffer
- 由于threejs基于性能的考虑,他一般只是读显存中的缓存。但是此时我们的BufferGeometry的调整还只是在内存中,我们需要在渲染的时候加上
line.geometry.attributes.position.needsUpdate = true
用于更新缓存
完整代码
scss
import { use,PlaneGeometry, Mesh, SceneControl as Scene, MeshBasicMaterial, Vector3, BufferGeometry, Line, LineBasicMaterial, BufferAttribute } from 'thunder-3d'
const scene = new Scene({
orbitControls: true,
defCameraOps: {
position: new Vector3(0, 10, 30)
}
})
scene.render(document.querySelector('#app')!)
const planeGeometry = new PlaneGeometry(100, 100)
const material = new MeshBasicMaterial({ color: 0xffffff, side: 2 })
const plane = new Mesh(planeGeometry, material)
plane.rotateX(-Math.PI / 2)
scene.add(plane)
const MAX_POINTS = 500;
const positions = new Float32Array(MAX_POINTS * 3);
let count = 0
const lineGeometry = new BufferGeometry()
lineGeometry.setAttribute('position', new BufferAttribute(positions, 3));
const lineMaterial = new LineBasicMaterial({ color: 0x000000, linewidth: 2 })
const line = new Line(lineGeometry, lineMaterial)
scene.add(line)
const setPoints = (point: Vector3) => {
positions[count * 3] = point.x
positions[count * 3 + 1] = point.y
positions[count * 3 + 2] = point.z
count++
line.geometry.setDrawRange(0, count);
console.log(count)
}
plane.addNatureEventListener('click', (object, intersect) => {
setPoints(intersect?.point!)
})
use.useframe(()=>{
line.geometry.attributes.position.needsUpdate = true
})
注: thunder-3d是笔者封的一个threejs生态库