diff --git a/src/composes/three/README.md b/src/composes/three/README.md new file mode 100644 index 0000000..914a402 --- /dev/null +++ b/src/composes/three/README.md @@ -0,0 +1,184 @@ +# Three.js 公共函数库 + +这是一个经过抽象的 Three.js 公共函数库,用于快速构建 3D 场景。 + +## 模块列表 + +### 1. useThreeScene - 场景管理 +创建和管理基础场景、相机、渲染器和控制器。 + +```typescript +import { useThreeScene } from '@/composes/three' + +const { createScene, createCamera, createRenderer, createControls } = useThreeScene() + +// 创建场景 +const scene = createScene({ + backgroundColor: 0x000000, + enableFog: true, + fogColor: 0x000000, + fogNear: 10, + fogFar: 50 +}) + +// 创建相机 +const camera = createCamera({ + fov: 75, + aspect: width / height, + position: { x: 0, y: 5, z: 15 } +}) + +// 创建渲染器 +const renderer = createRenderer(container, { + antialias: true, + shadowMap: true +}) + +// 创建控制器 +const controls = createControls(camera, renderer) +``` + +### 2. useEnvironment - 环境管理 +创建天空盒、背景、地面和流动效果。 + +```typescript +import { useEnvironment } from '@/composes/three' + +const envUtils = useEnvironment() + +// 创建海蓝色渐变背景 +envUtils.createOceanGradientBackground(scene) + +// 创建天空盒 +envUtils.createSkybox(scene, 0x87CEEB, 0x1A1A2E) + +// 创建有厚度的地板 +const ground = envUtils.createThickGround(100, 0.8, 0x2C3E50) +scene.add(ground) + +// 创建地板流动效果 +const flowingGround = envUtils.createFlowingGroundEffect(100) +scene.add(flowingGround.mesh) + +// 在动画循环中更新 +function animate() { + flowingGround.update(time) +} +``` + +### 3. useThreeLighting - 光照管理 +批量设置场景光照系统。 + +```typescript +import { useThreeLighting } from '@/composes/three' + +const lightingUtils = useThreeLighting() + +// 批量设置灯光 +lightingUtils.setupLighting(scene, { + ambientLight: true, + ambientIntensity: 0.8, + directionalLights: [ + { + color: 0xFFFFFF, + intensity: 1.5, + position: { x: 10, y: 15, z: 10 }, + castShadow: true + } + ], + pointLights: [ + { + color: 0x00FFFF, + intensity: 2, + distance: 20, + position: { x: 0, y: 5, z: 0 } + } + ] +}) +``` + +### 4. usePostProcessing - 后期处理 +创建后期处理效果。 + +```typescript +import { usePostProcessing } from '@/composes/three' + +const postUtils = usePostProcessing() + +// 创建后期处理组合器 +const composer = postUtils.createComposer(renderer, scene, camera) + +// 添加辉光效果 +const bloomPass = postUtils.createBloomPass(width, height, 1.5, 0.4, 0.85) +composer.addPass(bloomPass) + +// 添加轮廓描边 +const outlinePass = postUtils.createOutlinePass(width, height, scene, camera) +outlinePass.selectedObjects = [mesh] +composer.addPass(outlinePass) +``` + +### 5. useParticleSystem - 粒子系统 +创建和管理粒子效果(如蒸汽、烟雾)。 + +```typescript +import { useParticleSystem } from '@/composes/three' + +const particleUtils = useParticleSystem() + +// 创建粒子发射器 +const emitter = particleUtils.createEmitter( + scene, + new THREE.Vector3(0, 2, 0), + 5 // 发射速率 +) + +// 在动画循环中更新 +function animate() { + emitter.update() +} + +// 停止发射 +emitter.stop() +``` + +## 综合示例 + +参见 `src/views/three/12/index.vue` 获取完整的综合应用示例,包含: +- ✅ 海蓝色渐变天空 +- ✅ 立体天空盒 +- ✅ 有厚度的地板 +- ✅ 地板流动光效 +- ✅ 多光源照明系统 +- ✅ 粒子蒸汽效果 +- ✅ 设备告警与交互 +- ✅ 后期处理效果 + +## 文件结构 + +``` +src/composes/three/ +├── index.ts # 统一导出 +├── scene.ts # 场景管理 +├── environment.ts # 环境管理 +├── lighting.ts # 光照管理 +├── postprocessing.ts # 后期处理 +├── particles.ts # 粒子系统 +├── loader.ts # 加载器 +└── texture.ts # 纹理 +``` + +## 优势 + +1. **代码复用**:避免重复编写基础代码 +2. **类型安全**:完整的 TypeScript 类型定义 +3. **易于维护**:集中管理公共逻辑 +4. **快速开发**:通过组合函数快速搭建场景 +5. **灵活配置**:提供丰富的配置选项 + +## 使用建议 + +1. 根据项目需求选择需要的模块 +2. 可以在基础上扩展自定义功能 +3. 保持函数的纯粹性和可组合性 +4. 合理使用 TypeScript 类型提示 diff --git a/src/composes/three/environment.ts b/src/composes/three/environment.ts new file mode 100644 index 0000000..d0d8780 --- /dev/null +++ b/src/composes/three/environment.ts @@ -0,0 +1,144 @@ +import * as THREE from 'three' + +export function useEnvironment() { + /** + * 创建天空盒 + */ + function createSkybox(scene: THREE.Scene, color1 = 0x87CEEB, color2 = 0x1A1A2E) { + const skyGeometry = new THREE.BoxGeometry(500, 500, 500) + const skyMaterials = [ + new THREE.MeshBasicMaterial({ color: color1, side: THREE.BackSide }), // right + new THREE.MeshBasicMaterial({ color: color1, side: THREE.BackSide }), // left + new THREE.MeshBasicMaterial({ color: color2, side: THREE.BackSide }), // top + new THREE.MeshBasicMaterial({ color: color1, side: THREE.BackSide }), // bottom + new THREE.MeshBasicMaterial({ color: color1, side: THREE.BackSide }), // front + new THREE.MeshBasicMaterial({ color: color1, side: THREE.BackSide }), // back + ] + const skybox = new THREE.Mesh(skyGeometry, skyMaterials) + scene.add(skybox) + return skybox + } + + /** + * 创建海蓝色渐变背景 + */ + function createOceanGradientBackground(scene: THREE.Scene) { + // 使用canvas创建渐变纹理 + const canvas = document.createElement('canvas') + canvas.width = 2 + canvas.height = 256 + const ctx = canvas.getContext('2d')! + + const gradient = ctx.createLinearGradient(0, 0, 0, 256) + gradient.addColorStop(0, '#001a33') // 深海蓝 + gradient.addColorStop(0.5, '#004d80') // 中蓝 + gradient.addColorStop(1, '#0080bf') // 浅海蓝 + + ctx.fillStyle = gradient + ctx.fillRect(0, 0, 2, 256) + + const texture = new THREE.CanvasTexture(canvas) + scene.background = texture + return texture + } + + /** + * 创建有厚度的地板 + */ + function createThickGround( + size = 100, + thickness = 0.5, + color = 0x2C3E50, + ) { + const groundGeometry = new THREE.BoxGeometry(size, thickness, size) + const groundMaterial = new THREE.MeshStandardMaterial({ + color, + roughness: 0.8, + metalness: 0.2, + }) + + const ground = new THREE.Mesh(groundGeometry, groundMaterial) + ground.position.y = -thickness / 2 + ground.receiveShadow = true + ground.castShadow = true + + return ground + } + + /** + * 创建地板流动效果 + */ + function createFlowingGroundEffect(size = 100) { + // 创建自定义着色器材质 + const flowMaterial = new THREE.ShaderMaterial({ + transparent: true, + side: THREE.DoubleSide, + uniforms: { + time: { value: 0 }, + color1: { value: new THREE.Color(0x00FFFF) }, + color2: { value: new THREE.Color(0x0080FF) }, + }, + vertexShader: ` + varying vec2 vUv; + void main() { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); + } + `, + fragmentShader: ` + uniform float time; + uniform vec3 color1; + uniform vec3 color2; + varying vec2 vUv; + + void main() { + vec2 uv = vUv; + + // 创建流动的线条效果 + float line1 = sin(uv.x * 20.0 - time) * 0.5 + 0.5; + float line2 = sin(uv.y * 20.0 + time * 0.5) * 0.5 + 0.5; + float pattern = line1 * line2; + + // 添加移动的光带 + float wave = sin(uv.x * 5.0 - time * 2.0) * sin(uv.y * 5.0 + time * 1.5); + wave = smoothstep(0.3, 0.7, wave); + + vec3 color = mix(color1, color2, pattern); + float alpha = (pattern * 0.3 + wave * 0.5) * 0.4; + + gl_FragColor = vec4(color, alpha); + } + `, + }) + + const flowGeometry = new THREE.PlaneGeometry(size, size, 50, 50) + const flowMesh = new THREE.Mesh(flowGeometry, flowMaterial) + flowMesh.rotation.x = -Math.PI / 2 + flowMesh.position.y = 0.05 // 略高于地面 + + return { + mesh: flowMesh, + material: flowMaterial, + update: (time: number) => { + if (flowMaterial.uniforms.time) { + flowMaterial.uniforms.time.value = time + } + }, + } + } + + /** + * 创建网格辅助线 + */ + function createGrid(size = 100, divisions = 50, color1 = 0x444444, color2 = 0x222222) { + return new THREE.GridHelper(size, divisions, color1, color2) + } + + return { + createSkybox, + createOceanGradientBackground, + createThickGround, + createFlowingGroundEffect, + createGrid, + } +} diff --git a/src/composes/three/index.ts b/src/composes/three/index.ts index 9043896..43c3c9e 100644 --- a/src/composes/three/index.ts +++ b/src/composes/three/index.ts @@ -1,2 +1,7 @@ +export * from './environment' +export * from './lighting' export * from './loader' +export * from './particles' +export * from './postprocessing' +export * from './scene' export * from './texture' diff --git a/src/composes/three/lighting.ts b/src/composes/three/lighting.ts new file mode 100644 index 0000000..d4665ca --- /dev/null +++ b/src/composes/three/lighting.ts @@ -0,0 +1,117 @@ +import * as THREE from 'three' + +interface LightingSetup { + ambientLight?: boolean + ambientIntensity?: number + directionalLights?: Array<{ + color?: number + intensity?: number + position: { x: number, y: number, z: number } + castShadow?: boolean + }> + pointLights?: Array<{ + color?: number + intensity?: number + distance?: number + position: { x: number, y: number, z: number } + }> +} + +export function useThreeLighting() { + /** + * 创建环境光 + */ + function createAmbientLight(color = 0xFFFFFF, intensity = 1) { + return new THREE.AmbientLight(color, intensity) + } + + /** + * 创建方向光 + */ + function createDirectionalLight( + color = 0xFFFFFF, + intensity = 1, + position = { x: 10, y: 15, z: 10 }, + castShadow = true, + ) { + const light = new THREE.DirectionalLight(color, intensity) + light.position.set(position.x, position.y, position.z) + + if (castShadow) { + light.castShadow = true + light.shadow.mapSize.width = 2048 + light.shadow.mapSize.height = 2048 + light.shadow.camera.left = -20 + light.shadow.camera.right = 20 + light.shadow.camera.top = 20 + light.shadow.camera.bottom = -20 + } + + return light + } + + /** + * 创建点光源 + */ + function createPointLight( + color = 0xFFFFFF, + intensity = 1, + distance = 50, + position = { x: 0, y: 10, z: 0 }, + ) { + const light = new THREE.PointLight(color, intensity, distance) + light.position.set(position.x, position.y, position.z) + return light + } + + /** + * 批量设置灯光 + */ + function setupLighting(scene: THREE.Scene, config: LightingSetup) { + const lights: THREE.Light[] = [] + + // 环境光 + if (config.ambientLight !== false) { + const ambient = createAmbientLight(0xFFFFFF, config.ambientIntensity || 1) + scene.add(ambient) + lights.push(ambient) + } + + // 方向光 + if (config.directionalLights) { + config.directionalLights.forEach((lightConfig) => { + const light = createDirectionalLight( + lightConfig.color, + lightConfig.intensity, + lightConfig.position, + lightConfig.castShadow, + ) + scene.add(light) + lights.push(light) + }) + } + + // 点光源 + if (config.pointLights) { + config.pointLights.forEach((lightConfig) => { + const light = createPointLight( + lightConfig.color, + lightConfig.intensity, + lightConfig.distance, + lightConfig.position, + ) + scene.add(light) + lights.push(light) + }) + } + + return lights + } + + return { + createAmbientLight, + createDirectionalLight, + createPointLight, + setupLighting, + } +} diff --git a/src/composes/three/particles.ts b/src/composes/three/particles.ts new file mode 100644 index 0000000..73182ed --- /dev/null +++ b/src/composes/three/particles.ts @@ -0,0 +1,162 @@ +import * as THREE from 'three' + +export interface Particle { + sprite: THREE.Sprite + velocity: THREE.Vector3 + age: number + maxAge: number +} + +export function useParticleSystem() { + /** + * 创建烟雾纹理 + */ + function createSmokeTexture() { + const canvas = document.createElement('canvas') + canvas.width = 64 + canvas.height = 64 + const ctx = canvas.getContext('2d')! + + const gradient = ctx.createRadialGradient(32, 32, 0, 32, 32, 32) + gradient.addColorStop(0, 'rgba(255, 255, 255, 1)') + gradient.addColorStop(0.5, 'rgba(255, 255, 255, 0.5)') + gradient.addColorStop(1, 'rgba(255, 255, 255, 0)') + + ctx.fillStyle = gradient + ctx.fillRect(0, 0, 64, 64) + + const texture = new THREE.Texture(canvas) + texture.needsUpdate = true + return texture + } + + /** + * 创建粒子 + */ + function createParticle( + texture: THREE.Texture, + position: THREE.Vector3, + options: { + color?: number + opacity?: number + size?: number + velocity?: THREE.Vector3 + maxAge?: number + } = {}, + ): Particle { + const material = new THREE.SpriteMaterial({ + map: texture, + transparent: true, + opacity: options.opacity || 0.6, + color: options.color || 0xAAAAAA, + depthWrite: false, + blending: THREE.AdditiveBlending, + }) + + const sprite = new THREE.Sprite(material) + sprite.position.copy(position) + // 初始大小:可以通过 options.size 控制,默认 0.5 + sprite.scale.setScalar(options.size || 0.5) + + const velocity = options.velocity || new THREE.Vector3( + (Math.random() - 0.5) * 0.05, + 0.1 + Math.random() * 0.1, + (Math.random() - 0.5) * 0.05, + ) + + return { + sprite, + velocity, + age: 0, + maxAge: options.maxAge || 100, + } + } + + /** + * 更新粒子 + */ + function updateParticle(particle: Particle): boolean { + particle.sprite.position.add(particle.velocity) + particle.age++ + + // 更新大小和透明度 + const lifeRatio = particle.age / particle.maxAge + // 烟雾扩散:从初始大小逐渐变大,最大为初始大小的 3 倍 + const initialSize = 0.5 // 与 createParticle 中的默认 size 对应 + particle.sprite.scale.setScalar(initialSize + lifeRatio * initialSize * 2) + particle.sprite.material.opacity = 0.6 * (1 - lifeRatio) + + // 返回是否应该移除 + return particle.age >= particle.maxAge + } + + /** + * 创建粒子发射器 + */ + function createEmitter( + scene: THREE.Scene, + position: THREE.Vector3, + emissionRate = 5, + ) { + const texture = createSmokeTexture() + const particles: Particle[] = [] + let emissionCounter = 0 + + function emit() { + for (let i = 0; i < emissionRate; i++) { + const particlePos = position.clone() + particlePos.x += (Math.random() - 0.5) * 0.3 + particlePos.z += (Math.random() - 0.5) * 0.3 + + const particle = createParticle(texture, particlePos) + scene.add(particle.sprite) + particles.push(particle) + } + } + + function update() { + // 更新现有粒子 + for (let i = particles.length - 1; i >= 0; i--) { + const particle = particles[i] + if (!particle) + continue + + const shouldRemove = updateParticle(particle) + + if (shouldRemove) { + scene.remove(particle.sprite) + particle.sprite.material.dispose() + particles.splice(i, 1) + } + } + + // 控制发射频率 + emissionCounter++ + if (emissionCounter >= 2) { + emit() + emissionCounter = 0 + } + } + + function stop() { + particles.forEach((particle) => { + scene.remove(particle.sprite) + particle.sprite.material.dispose() + }) + particles.length = 0 + } + + return { + update, + stop, + particles, + } + } + + return { + createSmokeTexture, + createParticle, + updateParticle, + createEmitter, + } +} diff --git a/src/composes/three/postprocessing.ts b/src/composes/three/postprocessing.ts new file mode 100644 index 0000000..3b874b1 --- /dev/null +++ b/src/composes/three/postprocessing.ts @@ -0,0 +1,71 @@ +import * as THREE from 'three' +import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js' +import { OutlinePass } from 'three/examples/jsm/postprocessing/OutlinePass.js' +import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js' +import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass.js' + +export function usePostProcessing() { + /** + * 创建后期处理组合器 + */ + function createComposer( + renderer: THREE.WebGLRenderer, + scene: THREE.Scene, + camera: THREE.Camera, + ) { + const composer = new EffectComposer(renderer) + const renderPass = new RenderPass(scene, camera) + renderPass.clear = true + renderPass.clearDepth = true + composer.addPass(renderPass) + return composer + } + + /** + * 创建辉光通道 + */ + function createBloomPass( + width: number, + height: number, + strength = 1.5, + radius = 0.4, + threshold = 0.85, + ) { + return new UnrealBloomPass( + new THREE.Vector2(width, height), + strength, + radius, + threshold, + ) + } + + /** + * 创建轮廓描边通道 + */ + function createOutlinePass( + width: number, + height: number, + scene: THREE.Scene, + camera: THREE.Camera, + ) { + const outlinePass = new OutlinePass( + new THREE.Vector2(width, height), + scene, + camera, + ) + outlinePass.edgeStrength = 3 + outlinePass.edgeGlow = 0.5 + outlinePass.edgeThickness = 2 + outlinePass.pulsePeriod = 0 + outlinePass.visibleEdgeColor.set('#00ff00') + outlinePass.hiddenEdgeColor.set('#00ff00') + + return outlinePass + } + + return { + createComposer, + createBloomPass, + createOutlinePass, + } +} diff --git a/src/composes/three/scene.ts b/src/composes/three/scene.ts index a543c13..1e014b0 100644 --- a/src/composes/three/scene.ts +++ b/src/composes/three/scene.ts @@ -1,9 +1,117 @@ import * as THREE from 'three' +import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js' + +interface SceneOptions { + backgroundColor?: number + enableFog?: boolean + fogColor?: number + fogNear?: number + fogFar?: number +} + +interface CameraOptions { + fov?: number + aspect: number + near?: number + far?: number + position?: { x: number, y: number, z: number } +} + +interface RendererOptions { + antialias?: boolean + shadowMap?: boolean + shadowMapType?: THREE.ShadowMapType + toneMapping?: THREE.ToneMapping + toneMappingExposure?: number +} export function useThreeScene() { - function createScene(options) { + /** + * 创建场景 + */ + function createScene(options: SceneOptions = {}) { const scene = new THREE.Scene() + + if (options.backgroundColor !== undefined) { + scene.background = new THREE.Color(options.backgroundColor) + } + + if (options.enableFog) { + scene.fog = new THREE.Fog( + options.fogColor || 0x000000, + options.fogNear || 10, + options.fogFar || 50, + ) + } + + return scene } - return { createScene } + /** + * 创建相机 + */ + function createCamera(options: CameraOptions) { + const camera = new THREE.PerspectiveCamera( + options.fov || 75, + options.aspect, + options.near || 0.1, + options.far || 1000, + ) + + if (options.position) { + camera.position.set( + options.position.x, + options.position.y, + options.position.z, + ) + } + + return camera + } + + /** + * 创建渲染器 + */ + function createRenderer(container: HTMLElement, options: RendererOptions = {}) { + const renderer = new THREE.WebGLRenderer({ + antialias: options.antialias !== false, + }) + + const width = container.clientWidth + const height = container.clientHeight + + renderer.setSize(width, height) + renderer.setPixelRatio(window.devicePixelRatio) + + if (options.shadowMap !== false) { + renderer.shadowMap.enabled = true + renderer.shadowMap.type = options.shadowMapType || THREE.PCFSoftShadowMap + } + + if (options.toneMapping) { + renderer.toneMapping = options.toneMapping + renderer.toneMappingExposure = options.toneMappingExposure || 1 + } + + container.appendChild(renderer.domElement) + + return renderer + } + + /** + * 创建轨道控制器 + */ + function createControls(camera: THREE.Camera, renderer: THREE.WebGLRenderer) { + const controls = new OrbitControls(camera, renderer.domElement) + controls.enableDamping = true + controls.dampingFactor = 0.05 + return controls + } + + return { + createScene, + createCamera, + createRenderer, + createControls, + } } diff --git a/src/router/index.ts b/src/router/index.ts index dea81f4..60a9032 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -64,6 +64,11 @@ const router = createRouter({ name: 'three-11', component: () => import('../views/three/11/index.vue'), }, + { + path: '/three/12', + name: 'three-12', + component: () => import('../views/three/12/index.vue'), + }, { path: '/3d-point', name: '3d-point', diff --git a/src/views/three/10/index.vue b/src/views/three/10/index.vue index 4441262..86b3b55 100644 --- a/src/views/three/10/index.vue +++ b/src/views/three/10/index.vue @@ -3,7 +3,7 @@ * @Autor: 刘 相卿 * @Date: 2025-11-17 16:07:33 * @LastEditors: 刘 相卿 - * @LastEditTime: 2025-11-21 09:26:39 + * @LastEditTime: 2025-11-21 10:04:12 --> - 11 + + + + 公共函数抽象 + + + + + + 后期处理效果 + + + + + + + 1. 故障效果 (Glitch) + + + 数字故障、屏幕撕裂效果 + + + + + + + + 2. 抗锯齿 (SMAA) + + + 平滑边缘,减少锯齿 + + + + + + + + 3. 点效果 (Dot Screen) + + + 像素点阵、印刷效果 + + + + + + + + 4. 发光效果 (Bloom) + + + 明亮物体辉光效果 + + + + + 强度 + + {{ effects.bloomStrength }} + + + + + + + + + 5. 屏幕闪动 (Flicker) + + + 老旧屏幕闪烁效果 + + + + + + + + 6. 水底波浪 (Water) + + + 水下动态扭曲效果 + + + + + 可以同时开启多个效果组合 + + + + diff --git a/src/views/three/12/index.vue b/src/views/three/12/index.vue index 393a1c0..cab7597 100644 --- a/src/views/three/12/index.vue +++ b/src/views/three/12/index.vue @@ -1,13 +1,858 @@ - 12 + + + + + + + {{ selectedObject.name }} + + + 材质: + {{ selectedObject.material }} + + + 坐标: + + ({{ selectedObject.position.x.toFixed(2) }}, + {{ selectedObject.position.y.toFixed(2) }}, + {{ selectedObject.position.z.toFixed(2) }}) + + + + + + + + + + {{ dialogContent.title }} + + 告警 + + + × + + + + {{ dialogContent.description }} + + + ⚠️ + + + 温度:85°C(正常范围:≤70°C) + 压力:正常 + 运行时间:152小时 + + + + ✓ 温度:65°C + ✓ 压力:正常 + ✓ 运行时间:152小时 + + + + + + + + + 综合场景控制 + + + + 场景特性 + + ✓ 海蓝色渐变天空 + ✓ 立体天空盒 + ✓ 有厚度的地板 + ✓ 地板流动光效 + ✓ 多光源照明系统 + + + + + 设备控制 + + {{ isAlerting ? '关闭' : '触发' }}设备告警 + + + {{ isEmitting ? '关闭' : '开启' }}蒸汽效果 + + + + + 交互说明 + + 点击模型查看物体信息 + + + 点击设备 kzf_peij_18 查看详情 + + + 点击空白处取消选择 + + + + + 使用的公共函数: + useThreeScene() + useEnvironment() + useThreeLighting() + usePostProcessing() + useParticleSystem() + + + +
{{ dialogContent.description }}