name: threejs-tresjs description: 使用 Three.js 和 TresJS 为 JARVIS AI 助手进行 3D HUD 渲染 model: sonnet risk_level: MEDIUM version: 1.0.0
Three.js / TresJS 开发技能
文件组织: 此技能采用分割结构。查看
references/获取高级模式和安全示例。
1. 概述
此技能提供使用 Three.js 和 TresJS(Vue 3 集成)构建 3D HUD 界面的专业知识。专注于为 JARVIS AI 助手创建高性能、视觉震撼的全息显示。
风险等级: MEDIUM - GPU 资源消耗,颜色解析中的潜在 ReDoS,WebGL 安全考虑
主要用例:
- 渲染 3D 全息 HUD 面板
- 动画状态指示器和仪表
- 系统可视化的粒子效果
- 带有 3D 元素的实时指标显示
2. 核心职责
2.1 基本原则
- 测试驱动开发优先: 在实现之前编写测试 - 验证 3D 组件正确渲染
- 性能意识: 通过实例化、LOD 和高效渲染循环优化至 60fps
- 资源管理: 始终处理几何体、材质和纹理以防止内存泄漏
- Vue 响应式集成: 使用 TresJS 实现无缝 Vue 3 组合 API 集成
- 安全颜色解析: 验证颜色输入以防止 ReDoS 攻击
- GPU 保护: 实施防止 GPU 资源耗尽的保障措施
- 可访问性: 为不支持 WebGL 的设备提供回退方案
3. 技术栈和版本
3.1 推荐版本
| 包 | 版本 | 安全说明 |
|---|---|---|
| three | ^0.160.0+ | 最新稳定版,修复 CVE-2020-28496 ReDoS |
| @tresjs/core | ^4.0.0 | Vue 3 集成 |
| @tresjs/cientos | ^3.0.0 | 组件库 |
| postprocessing | ^6.0.0 | 效果库 |
3.2 安全关键更新
{
"dependencies": {
"three": "^0.160.0",
"@tresjs/core": "^4.0.0",
"@tresjs/cientos": "^3.0.0"
}
}
注意: 版本 0.137.0 之前有 XSS 漏洞,0.125.0 之前有 ReDoS 漏洞。
4. 实现模式
4.1 基本 HUD 场景设置
<script setup lang="ts">
import { TresCanvas } from '@tresjs/core'
import { OrbitControls } from '@tresjs/cientos'
const gl = {
clearColor: '#000011',
alpha: true,
antialias: true,
powerPreference: 'high-performance'
}
</script>
<template>
<TresCanvas v-bind="gl">
<TresPerspectiveCamera :position="[0, 0, 5]" />
<OrbitControls :enable-damping="true" />
<HUDPanels />
<MetricsDisplay />
<ParticleEffects />
</TresCanvas>
</template>
4.2 安全颜色处理
// utils/safeColor.ts
import { Color } from 'three'
// ✅ 带有验证的安全颜色解析
export function safeParseColor(input: string): Color {
// 验证格式以防止 ReDoS
const hexPattern = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/
const rgbPattern = /^rgb\(\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}\s*\)$/
if (!hexPattern.test(input) && !rgbPattern.test(input)) {
console.warn('无效颜色格式,使用默认值')
return new Color(0x00ff00) // 默认 JARVIS 绿色
}
return new Color(input)
}
// ❌ 危险 - 用户输入直接到 Color
// const color = new Color(userInput) // 潜在 ReDoS
// ✅ 安全 - 已验证输入
const color = safeParseColor(userInput)
4.3 内存安全组件
<script setup lang="ts">
import { onUnmounted, shallowRef } from 'vue'
import { Mesh, BoxGeometry, MeshStandardMaterial } from 'three'
// ✅ 对 Three.js 对象使用 shallowRef
const meshRef = shallowRef<Mesh | null>(null)
// ✅ 卸载时清理
onUnmounted(() => {
if (meshRef.value) {
meshRef.value.geometry.dispose()
if (Array.isArray(meshRef.value.material)) {
meshRef.value.material.forEach(m => m.dispose())
} else {
meshRef.value.material.dispose()
}
}
})
</script>
<template>
<TresMesh ref="meshRef">
<TresBoxGeometry :args="[1, 1, 1]" />
<TresMeshStandardMaterial color="#00ff41" />
</TresMesh>
</template>
4.4 性能优化的实例化
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { InstancedMesh, Object3D, Matrix4 } from 'three'
const instanceCount = 1000
const instancedMeshRef = ref<InstancedMesh | null>(null)
onMounted(() => {
if (!instancedMeshRef.value) return
const dummy = new Object3D()
const matrix = new Matrix4()
// ✅ 批量更新以提高性能
for (let i = 0; i < instanceCount; i++) {
dummy.position.set(
Math.random() * 10 - 5,
Math.random() * 10 - 5,
Math.random() * 10 - 5
)
dummy.updateMatrix()
instancedMeshRef.value.setMatrixAt(i, dummy.matrix)
}
instancedMeshRef.value.instanceMatrix.needsUpdate = true
})
</script>
<template>
<TresInstancedMesh ref="instancedMeshRef" :args="[null, null, instanceCount]">
<TresSphereGeometry :args="[0.05, 8, 8]" />
<TresMeshBasicMaterial color="#00ff41" />
</TresInstancedMesh>
</template>
4.5 带有文本的 HUD 面板
<script setup lang="ts">
import { Text } from '@tresjs/cientos'
const props = defineProps<{
title: string
value: number
}>()
// ✅ 消毒文本内容
const safeTitle = computed(() =>
props.title.replace(/[<>]/g, '').slice(0, 50)
)
</script>
<template>
<TresGroup>
<!-- 面板背景 -->
<TresMesh>
<TresPlaneGeometry :args="[2, 1]" />
<TresMeshBasicMaterial
color="#001122"
:transparent="true"
:opacity="0.8"
/>
</TresMesh>
<!-- 标题文本 -->
<Text
:text="safeTitle"
:font-size="0.15"
color="#00ff41"
:position="[-0.8, 0.3, 0.01]"
/>
<!-- 值显示 -->
<Text
:text="String(props.value)"
:font-size="0.3"
color="#ffffff"
:position="[0, -0.1, 0.01]"
/>
</TresGroup>
</template>
5. 实现工作流程(TDD)
5.1 3D 组件的 TDD 流程
步骤 1: 首先编写失败测试
// tests/components/hud-panel.test.ts
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { mount, VueWrapper } from '@vue/test-utils'
import { Scene, WebGLRenderer } from 'three'
import HUDPanel from '~/components/hud/HUDPanel.vue'
describe('HUDPanel', () => {
let wrapper: VueWrapper
beforeEach(() => {
// 为测试模拟 WebGL 上下文
const canvas = document.createElement('canvas')
const gl = canvas.getContext('webgl2')
vi.spyOn(HTMLCanvasElement.prototype, 'getContext').mockReturnValue(gl)
})
afterEach(() => {
wrapper?.unmount()
vi.restoreAllMocks()
})
it('以正确尺寸渲染面板', () => {
wrapper = mount(HUDPanel, {
props: { width: 2, height: 1, title: 'Status' }
})
// 测试失败直到组件实现
expect(wrapper.exists()).toBe(true)
})
it('在卸载时处理资源', async () => {
wrapper = mount(HUDPanel, {
props: { width: 2, height: 1, title: 'Status' }
})
const disposeSpy = vi.fn()
wrapper.vm.meshRef.geometry.dispose = disposeSpy
wrapper.unmount()
expect(disposeSpy).toHaveBeenCalled()
})
})
步骤 2: 实现最小通过量
<script setup lang="ts">
import { shallowRef, onUnmounted } from 'vue'
import { Mesh } from 'three'
const props = defineProps<{
width: number
height: number
title: string
}>()
const meshRef = shallowRef<Mesh | null>(null)
onUnmounted(() => {
if (meshRef.value) {
meshRef.value.geometry.dispose()
;(meshRef.value.material as any).dispose()
}
})
</script>
<template>
<TresMesh ref="meshRef">
<TresPlaneGeometry :args="[props.width, props.height]" />
<TresMeshBasicMaterial color="#001122" :transparent="true" :opacity="0.8" />
</TresMesh>
</template>
步骤 3: 遵循模式重构
// 测试通过后,添加性能优化
// - 对多个面板使用实例化
// - 为远处面板添加 LOD
// - 为文本实现纹理图集
步骤 4: 运行完整验证
# 运行所有测试
npm test
# 运行带覆盖率的测试
npm test -- --coverage
# 类型检查
npm run typecheck
# 性能基准测试
npm run test:perf
5.2 测试 3D 动画
import { describe, it, expect, vi } from 'vitest'
import { useRenderLoop } from '@tresjs/core'
describe('动画循环', () => {
it('在动画期间保持 60fps', async () => {
const frameTimes: number[] = []
let lastTime = performance.now()
const { onLoop } = useRenderLoop()
onLoop(() => {
const now = performance.now()
frameTimes.push(now - lastTime)
lastTime = now
})
// 模拟 60 帧
await new Promise(resolve => setTimeout(resolve, 1000))
const avgFrameTime = frameTimes.reduce((a, b) => a + b, 0) / frameTimes.length
expect(avgFrameTime).toBeLessThan(16.67) // 60fps = 每帧 16.67ms
})
it('在卸载时清理动画循环', () => {
const cleanup = vi.fn()
const { pause } = useRenderLoop()
// 组件卸载
pause()
expect(cleanup).not.toThrow()
})
})
5.3 测试资源处理
describe('资源管理', () => {
it('处理所有 GPU 资源', () => {
const geometry = new BoxGeometry(1, 1, 1)
const material = new MeshStandardMaterial({ color: 0x00ff41 })
const mesh = new Mesh(geometry, material)
const geoDispose = vi.spyOn(geometry, 'dispose')
const matDispose = vi.spyOn(material, 'dispose')
// 清理函数
mesh.geometry.dispose()
mesh.material.dispose()
expect(geoDispose).toHaveBeenCalled()
expect(matDispose).toHaveBeenCalled()
})
it('正确处理材质数组', () => {
const materials = [
new MeshBasicMaterial(),
new MeshStandardMaterial()
]
const mesh = new Mesh(new BoxGeometry(), materials)
const spies = materials.map(m => vi.spyOn(m, 'dispose'))
materials.forEach(m => m.dispose())
spies.forEach(spy => expect(spy).toHaveBeenCalled())
})
})
6. 性能模式
6.1 几何体实例化
// 好: 对重复对象使用 InstancedMesh
import { InstancedMesh, Matrix4, Object3D } from 'three'
const COUNT = 1000
const mesh = new InstancedMesh(geometry, material, COUNT)
const dummy = new Object3D()
for (let i = 0; i < COUNT; i++) {
dummy.position.set(Math.random() * 10, Math.random() * 10, Math.random() * 10)
dummy.updateMatrix()
mesh.setMatrixAt(i, dummy.matrix)
}
mesh.instanceMatrix.needsUpdate = true
// 坏: 创建单个网格
for (let i = 0; i < COUNT; i++) {
const mesh = new Mesh(geometry.clone(), material.clone()) // 内存浪费!
scene.add(mesh)
}
6.2 纹理图集
// 好: 对多个精灵使用单个纹理图集
const atlas = new TextureLoader().load('/textures/hud-atlas.png')
const materials = {
panel: new SpriteMaterial({ map: atlas }),
icon: new SpriteMaterial({ map: atlas })
}
// 为不同精灵设置 UV 偏移
materials.panel.map.offset.set(0, 0.5)
materials.panel.map.repeat.set(0.5, 0.5)
// 坏: 加载单独纹理
const panelTex = new TextureLoader().load('/textures/panel.png')
const iconTex = new TextureLoader().load('/textures/icon.png')
// 多个绘制调用,更多 GPU 内存
6.3 细节层次 (LOD)
// 好: 对复杂对象使用 LOD
import { LOD } from 'three'
const lod = new LOD()
// 高细节 - 近距离
const highDetail = new Mesh(
new SphereGeometry(1, 32, 32),
material
)
lod.addLevel(highDetail, 0)
// 中等细节 - 中距离
const medDetail = new Mesh(
new SphereGeometry(1, 16, 16),
material
)
lod.addLevel(medDetail, 10)
// 低细节 - 远距离
const lowDetail = new Mesh(
new SphereGeometry(1, 8, 8),
material
)
lod.addLevel(lowDetail, 20)
scene.add(lod)
// 坏: 始终渲染高细节
const sphere = new Mesh(new SphereGeometry(1, 64, 64), material)
6.4 视锥体剔除
// 好: 启用视锥体剔除(默认,但验证)
mesh.frustumCulled = true
// 对于自定义边界优化
mesh.geometry.computeBoundingSphere()
mesh.geometry.computeBoundingBox()
// 对复杂场景的手动可见性检查
const frustum = new Frustum()
const matrix = new Matrix4().multiplyMatrices(
camera.projectionMatrix,
camera.matrixWorldInverse
)
frustum.setFromProjectionMatrix(matrix)
objects.forEach(obj => {
obj.visible = frustum.intersectsObject(obj)
})
// 坏: 禁用剔除或渲染所有内容
mesh.frustumCulled = false // 即使离屏也渲染
6.5 对象池
// 好: 池化和重用对象
class ParticlePool {
private pool: Mesh[] = []
private active: Set<Mesh> = new Set()
constructor(private geometry: BufferGeometry, private material: Material) {
// 预分配池
for (let i = 0; i < 100; i++) {
const mesh = new Mesh(geometry, material)
mesh.visible = false
this.pool.push(mesh)
}
}
acquire(): Mesh | null {
const mesh = this.pool.find(m => !this.active.has(m))
if (mesh) {
mesh.visible = true
this.active.add(mesh)
return mesh
}
return null
}
release(mesh: Mesh): void {
mesh.visible = false
this.active.delete(mesh)
}
}
// 坏: 每帧创建/销毁对象
function spawnParticle() {
const mesh = new Mesh(geometry, material) // GC 压力!
scene.add(mesh)
setTimeout(() => {
scene.remove(mesh)
mesh.geometry.dispose()
}, 1000)
}
6.6 RAF 优化
// 好: 高效渲染循环
let lastTime = 0
const targetFPS = 60
const frameInterval = 1000 / targetFPS
function animate(currentTime: number) {
requestAnimationFrame(animate)
const delta = currentTime - lastTime
// 如果太早,跳过帧(为省电)
if (delta < frameInterval) return
lastTime = currentTime - (delta % frameInterval)
// 只更新改变的内容
if (needsUpdate) {
updateScene()
renderer.render(scene, camera)
}
}
// 坏: 无条件渲染每帧
function animate() {
requestAnimationFrame(animate)
// 总是更新所有内容
updateAllObjects()
renderer.render(scene, camera) // 即使没有变化
}
6.7 着色器优化
// 好: 简单、优化的着色器
const material = new ShaderMaterial({
vertexShader: `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
varying vec2 vUv;
uniform vec3 color;
void main() {
gl_FragColor = vec4(color, 1.0);
}
`,
uniforms: {
color: { value: new Color(0x00ff41) }
}
})
// 坏: 在片段着色器中复杂计算
// 避免: 循环、条件语句、可能时的纹理查找
7. 安全标准
7.1 已知漏洞
| CVE | 严重性 | 描述 | 缓解措施 |
|---|---|---|---|
| CVE-2020-28496 | HIGH | 颜色解析中的 ReDoS | 更新到 0.125.0+,验证颜色 |
| CVE-2022-0177 | MEDIUM | 文档中的 XSS | 更新到 0.137.0+ |
7.2 OWASP Top 10 覆盖
| OWASP 类别 | 风险 | 缓解措施 |
|---|---|---|
| A05 注入 | MEDIUM | 验证所有颜色/文本输入 |
| A06 易受攻击组件 | HIGH | 保持 Three.js 更新 |
7.3 GPU 资源保护
// composables/useResourceLimit.ts
export function useResourceLimit() {
const MAX_TRIANGLES = 1_000_000
const MAX_DRAW_CALLS = 100
let triangleCount = 0
function checkGeometry(geometry: BufferGeometry): boolean {
const triangles = geometry.index
? geometry.index.count / 3
: geometry.attributes.position.count / 3
if (triangleCount + triangles > MAX_TRIANGLES) {
console.error('三角形限制超出')
return false
}
triangleCount += triangles
return true
}
return { checkGeometry }
}
8. 常见错误和反模式
8.1 关键安全反模式
从不: 直接解析用户颜色
// ❌ 危险 - ReDoS 漏洞
const color = new Color(userInput)
// ✅ 安全 - 已验证输入
const color = safeParseColor(userInput)
从不: 跳过资源处理
// ❌ 内存泄漏
const mesh = new Mesh(geometry, material)
scene.remove(mesh)
// 几何体和材质仍在 GPU 内存中!
// ✅ 适当清理
scene.remove(mesh)
mesh.geometry.dispose()
mesh.material.dispose()
8.2 性能反模式
避免: 在渲染循环中创建对象
// ❌ 坏 - 每帧创建垃圾
function animate() {
mesh.position.add(new Vector3(0, 0.01, 0)) // 每帧新对象!
}
// ✅ 好 - 重用对象
const velocity = new Vector3(0, 0.01, 0)
function animate() {
mesh.position.add(velocity)
}
13. 预部署检查清单
安全验证
- [ ] Three.js 版本 >= 0.137.0(XSS 修复)
- [ ] 所有颜色输入在解析前已验证
- [ ] 没有用户输入直接到
new Color() - [ ] 资源限制已执行
性能验证
- [ ] 所有几何体/材质在卸载时已处理
- [ ] 对重复对象使用实例化
- [ ] 渲染循环中没有对象创建
- [ ] 对复杂场景实现 LOD
14. 总结
Three.js/TresJS 为 JARVIS HUD 提供 3D 渲染:
- 安全: 验证所有输入,特别是颜色以防止 ReDoS
- 内存: 始终在组件卸载时处理资源
- 性能: 使用实例化,避免在渲染循环中分配
- 集成: TresJS 提供无缝 Vue 3 响应性
记住: WebGL 有直接 GPU 访问 - 始终验证输入并小心管理资源。
参考:
references/advanced-patterns.md- 复杂 3D 模式references/security-examples.md- WebGL 安全实践