Three.js 线条绘制指南
1. 基础概念
1.1 线条类型
THREE.Line
: 基础线条,连接点之间形成直线段THREE.LineLoop
: 闭合线条,自动连接首尾点THREE.LineSegments
: 线段,每两个点之间形成独立线段
1.2 线条材质
- 基础线条材质 (
LineBasicMaterial
)
javascript
const material = new THREE.LineBasicMaterial({
color: 0x00ff00, // 线条颜色
linewidth: 1, // 线宽(WebGL限制为1)
linecap: 'round', // 线条端点样式
linejoin: 'round' // 线条连接处样式
})
- 虚线材质 (
LineDashedMaterial
)
javascript
const material = new THREE.LineDashedMaterial({
color: 0xff0000, // 线条颜色
dashSize: 0.5, // 虚线段长度
gapSize: 0.2, // 间隙长度
scale: 1 // 虚线模式的整体缩放
})
2. 创建线条的方法
2.1 使用点数组创建
javascript
const points = []
points.push(new THREE.Vector3(-1, 0, 0))
points.push(new THREE.Vector3(1, 0, 0))
const geometry = new THREE.BufferGeometry().setFromPoints(points)
const line = new THREE.Line(geometry, material)
2.2 使用 BufferGeometry
javascript
const geometry = new THREE.BufferGeometry()
const positions = new Float32Array([
-1, 0, 0, // 第一个点
1, 0, 0 // 第二个点
])
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3))
3. 常见线条效果
3.1 正弦波形线条
javascript
const points = []
for (let i = 0; i <= 100; i++) {
const x = i * 0.1 - 5
const y = Math.sin(x)
points.push(new THREE.Vector3(x, y, 0))
}
3.2 圆形轨迹
javascript
const points = []
for (let i = 0; i <= 360; i++) {
const angle = (i * Math.PI) / 180
const x = Math.cos(angle) * radius
const y = Math.sin(angle) * radius
points.push(new THREE.Vector3(x, y, 0))
}
3.3 动态波浪线条
javascript
const updateLine = (time) => {
const positions = line.geometry.attributes.position.array
for (let i = 0; i < points.length; i++) {
positions[i * 3 + 1] = Math.sin(i * 0.2 + time)
}
line.geometry.attributes.position.needsUpdate = true
}
4. 性能优化
4.1 几何体优化
- 使用
BufferGeometry
而不是普通Geometry
- 适当控制点的数量
- 及时清理不需要的几何体
4.2 更新优化
- 仅在必要时更新顶点位置
- 使用
needsUpdate
标记更新 - 避免频繁创建新的几何体
4.3 内存管理
javascript
// 清理资源
geometry.dispose()
material.dispose()
```line.md
## 5. 注意事项
1. WebGL 限制
- 线宽在 WebGL 中始终为 1 像素
- 某些图形卡可能不支持虚线效果
2. 虚线渲染
- 使用虚线时需要调用 `computeLineDistances()`
- 虚线模式可能影响性能
3. 动画性能
- 频繁更新顶点位置时注意性能开销
- 考虑使用 GPU 计算来优化大量点的更新
## 6. 最佳实践
1. 初始化
- 预先创建足够的顶点缓冲
- 合理设置相机位置和视角
2. 更新
- 使用 requestAnimationFrame 进行动画更新
- 在组件卸载时清理资源
3. 响应式
- 监听窗口大小变化
- 更新渲染器和相机参数
4. 错误处理
- 检查 WebGL 支持
- 提供降级方案
vue3 + three 画线
代码如下
html
<template>
<div class="three-container" ref="container">
<!-- WebGL不支持时显示的错误信息 -->
<div v-if="!webglSupported" class="webgl-error">
<h2>WebGL不可用</h2>
<p>{{ webglError }}</p>
</div>
</div>
</template>
<script setup>
import * as THREE from 'three'
import { onMounted, onBeforeUnmount, ref } from 'vue'
import { checkWebGL2Support } from '@/utils/webglCheck'
// 创建DOM引用
const container = ref(null)
// 声明Three.js全局变量
let scene, camera, renderer
let animationFrameId
// WebGL支持状态
const webglSupported = ref(true)
const webglError = ref('')
/**
* 创建基础线条
* @returns {THREE.Line} 线条对象
*/
const createBasicLine = () => {
// 创建线条的点位数组
const points = []
// 创建一个正弦波形的线条
for (let i = 0; i <= 100; i++) {
const x = i * 0.1 - 5 // x轴范围 -5 到 5
const y = Math.sin(x) // y = sin(x)
const z = 0
points.push(new THREE.Vector3(x, y, z))
}
// 创建线条几何体
const geometry = new THREE.BufferGeometry().setFromPoints(points)
// 创建线条材质
const material = new THREE.LineBasicMaterial({
color: 0x00ff00, // 绿色
linewidth: 1 // 线宽(注意:在WebGL中线宽始终为1)
})
// 创建线条对象
return new THREE.Line(geometry, material)
}
/**
* 创建虚线
* @returns {THREE.Line} 虚线对象
*/
const createDashedLine = () => {
const points = []
// 创建一个圆形轨迹的点
for (let i = 0; i <= 360; i++) {
const angle = (i * Math.PI) / 180
const x = Math.cos(angle) * 2 // 半径为2的圆
const y = Math.sin(angle) * 2
const z = 0
points.push(new THREE.Vector3(x, y - 5, z)) // 向下偏移5个单位
}
const geometry = new THREE.BufferGeometry().setFromPoints(points)
// 使用虚线材质
const material = new THREE.LineDashedMaterial({
color: 0xff0000, // 红色
dashSize: 0.5, // 虚线段长度
gapSize: 0.2 // 间隙长度
})
// 创建虚线
const line = new THREE.Line(geometry, material)
// 计算虚线段
line.computeLineDistances()
return line
}
/**
* 创建动态线条
* @returns {Object} 包含线条对象和更新函数
*/
const createAnimatedLine = () => {
const points = []
// 初始点
for (let i = 0; i <= 50; i++) {
points.push(new THREE.Vector3(i * 0.2 - 5, 0, 0))
}
const geometry = new THREE.BufferGeometry()
// 创建顶点位置属性数组
const positions = new Float32Array(points.length * 3)
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3))
const material = new THREE.LineBasicMaterial({
color: 0x0000ff // 蓝色
})
const line = new THREE.Line(geometry, material)
// 更新函数 - 创建波浪动画
const updateLine = (time) => {
const positions = line.geometry.attributes.position.array
for (let i = 0; i < points.length; i++) {
const x = i * 0.2 - 5
// 使用时间创建动态波浪效果
const y = Math.sin(x + time) * Math.cos(time * 0.5)
positions[i * 3] = x
positions[i * 3 + 1] = y + 3 // 向上偏移3个单位
positions[i * 3 + 2] = 0
}
line.geometry.attributes.position.needsUpdate = true
}
return { line, updateLine }
}
/**
* 初始化3D场景
*/
const initScene = (containerEl) => {
// 检查WebGL2支持
const webglStatus = checkWebGL2Support()
if (!webglStatus.supported) {
webglSupported.value = false
webglError.value = webglStatus.reason
return
}
try {
// 创建场景
scene = new THREE.Scene()
scene.background = new THREE.Color(0x111111) // 深色背景
// 创建相机
camera = new THREE.PerspectiveCamera(
75,
containerEl.clientWidth / containerEl.clientHeight,
0.1,
1000
)
camera.position.z = 10
// 创建渲染器
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
})
renderer.setSize(containerEl.clientWidth, containerEl.clientHeight)
renderer.setPixelRatio(window.devicePixelRatio)
containerEl.appendChild(renderer.domElement)
// 添加各种线条
const basicLine = createBasicLine()
scene.add(basicLine)
const dashedLine = createDashedLine()
scene.add(dashedLine)
const { line: animatedLine, updateLine } = createAnimatedLine()
scene.add(animatedLine)
// 动画循环
const animate = () => {
animationFrameId = requestAnimationFrame(animate)
// 更新动态线条
updateLine(Date.now() * 0.001)
renderer.render(scene, camera)
}
animate()
} catch (error) {
webglSupported.value = false
webglError.value = `初始化3D场景失败: ${error.message}`
console.error('场景初始化错误:', error)
}
}
/**
* 处理窗口大小变化
*/
const handleResize = () => {
if (!renderer || !camera) return
const containerEl = renderer.domElement.parentElement
const width = containerEl.clientWidth
const height = containerEl.clientHeight
camera.aspect = width / height
camera.updateProjectionMatrix()
renderer.setSize(width, height)
}
// 生命周期钩子
onMounted(() => {
initScene(container.value)
window.addEventListener('resize', handleResize)
})
onBeforeUnmount(() => {
window.removeEventListener('resize', handleResize)
cancelAnimationFrame(animationFrameId)
if (renderer) {
renderer.dispose()
}
// 清理场景中的所有线条
scene?.traverse((object) => {
if (object instanceof THREE.Line) {
object.geometry.dispose()
object.material.dispose()
}
})
})
// 组件名定义
defineOptions({
name: 'ThreeLine'
})
</script>
<style scoped>
.three-container {
width: 100%;
height: 100vh;
overflow: hidden;
position: relative;
}
.webgl-error {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
background: rgba(255, 255, 255, 0.95);
padding: 2rem;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
</style>