Three.js 实战之电子围栏可根据模型自动生成

Three.js 实战之电子围栏------可根据模型自动生成

在三维可视化或数字孪生项目中,光墙(Light Wall)是一种常见的视觉效果,常用于强调场景区域、形成边界或增加科技未来感。本文将基于 Three.js,深入解析一个 useLightWall 类的实现,展示如何通过几何体、材质与动画组合实现不同类型的光墙效果。

立方光墙

圆形光墙

点光墙(自定义)

1. 功能概述

useLightWall 继承自 THREE.Mesh,提供了一种灵活的光墙创建方式。通过不同的参数配置,开发者可以生成三类光墙:

  • 圆形光墙(round) :基于圆柱几何体生成,适合用于场景区域环绕。
  • 立方光墙(cube) :通过手动拼接顶点与面生成,常用于方形区域包围。
  • 点光墙(points) :根据自定义点序列生成,可构造任意多边形边界光墙。

除此之外,还支持:

  • 自定义颜色与透明度
  • 纹理贴图(map)
  • 可选的动画(方向 + 速度)
  • 支持闭合与非闭合路径

2. 核心类结构

js 复制代码
import { Mesh,MeshBasicMaterial,DoubleSide} from 'three';
export class useLightWall extends Mesh {
    constructor(options = {}) {
        const geometry = useLightWall.createGeometryFromPath(options);

        super(geometry);
        this.material = new MeshBasicMaterial({// 基础网格材质 可以使用任意其它材质
            color: options.color ?? 0x4286f4,
            map: options.map,
            side: DoubleSide,
            transparent: true,
            opacity: options.opacity ?? 0.5

        });

        this.renderOrder = 1;
        // 是否支持动画
        const animation = options.animation ?? true;
        // 动画方向 默认 从下到上
        const direction = options.direction || 'y';
        // 运动速度
        const speed = options.speed ?? 0.2;
        const start = Date.now();
        this.onBeforeRender = () => {
            if (animation && this.material.map) {
                const offset = ((Date.now() - start) / 1000) * speed;
                this.material.map.offset[direction] = -offset;
            }
            
        };

    }
}

3. 几何体生成方法

js 复制代码
static createGeometryFromPath(options) {
    const type = options.type ?? 'round';
    if(type === 'round'){// 圆
       return geometry["round"]({radius: options.radius ?? 10, height: options.height ?? 10})
    }
    if(type === 'cube'){ // 立方体
        return geometry["cube"]({width: options.width ?? 5, length: options.length ?? 10, height: options.height ?? 5})
   }
   if(type === 'points'){ //自定义
       return geometry["points"]({points: options.points || [] ,height: options.height ?? 0.1,isClosed: options.isClosed ?? true})
   }
        
}

4. 圆形光墙生成方法

js 复制代码
import { CylinderGeometry} from 'three';
const geometry = {
     // 使用圆柱缓冲几何体
    "round": (options)=> new CylinderGeometry(options.radius, options.radius, options.height, 1000, 1, true),
}

圆形使用示例

js 复制代码
import { TextureLoader, SRGBColorSpace, RepeatWrapping } from 'three';
import imgLine from '@/assets/images/wall/vertical-line.png'
 // 加载图片 map
const textureLoader =  (url)=>{
      const textureLoader = new TextureLoader()
      const texture = textureLoader.load(url)
      texture.colorSpace = SRGBColorSpace;
      texture.wrapS  = RepeatWrapping;
      texture.wrapT  = RepeatWrapping;
      return texture
}
const wallImg =  utils.textureLoader(imgLine)
wallImg.repeat.set(10,2)
const wall = new useLightWall({
    map: wallImg,
    color: 0xf12711,//颜色
    opacity: 1.0, // 透明度
    radius: 20, // 半径
    height: 40, // 高度
    type: 'round', // 类型
    speed: 0.2, // 速度
    animation: true // 是否启用动画 默认true
 })
 // 省略 设置坐标position 以及 rotation 根据自己实际场景进行设置
 this.scene.add(wall)

5. 立方体生成方法

js 复制代码
import { BufferGeometry} from 'three';
const geometry = {
     // 使用BufferGeometry
    "cube": ({width,length,height}) =>{
        const c = [ 0, 0,1, 0,1, -1,0, -1,0, 0]
        const geometry = new BufferGeometry(); //声明一个空几何体对象
        const posArr = [];
        const uvrr = [];
        for (let i = 0; i < c.length - 2; i += 2) {
            posArr.push(c[i] * width, c[i + 1] * length, 0);
            posArr.push(c[i + 2] * width, c[i + 3] * length, 0);
            posArr.push(c[i + 2] * width, c[i + 3] * length, height);
          
            posArr.push(c[i] * width, c[i + 1] * length, 0);
            posArr.push(c[i + 2] * width, c[i + 3] * length, height);
            posArr.push(c[i] * width, c[i + 1] * length, height);
          
          
            // 注意顺序问题,和顶点位置坐标对应
            uvrr.push(0, 0, 1, 0, 1, 1);
            uvrr.push(0, 0, 1, 1, 0, 1);
        }
        geometry.attributes.position = new BufferAttribute(new Float32Array(posArr), 3);
        geometry.attributes.uv = new BufferAttribute(new Float32Array(uvrr), 2);
        geometry.computeVertexNormals()
        return geometry
    },
}

立方体使用示例

js 复制代码
import { TextureLoader, SRGBColorSpace, RepeatWrapping } from 'three';
import imgLine from '@/assets/images/wall/vertical-line.png'
const textureLoader =  (url)=>{
      const textureLoader = new TextureLoader()
      const texture = textureLoader.load(url)
      texture.colorSpace = SRGBColorSpace;
      texture.wrapS  = RepeatWrapping;
      texture.wrapT  = RepeatWrapping;
      return texture
}
const wallImg =  utils.textureLoader(imgLine)
wallImg.repeat.set(10,2)
const wall = new useLightWall({
    map: wallImg,
    color: 0xf12711,
    opacity: 1.0,
    height: 40,  // 可选 光墙高度
    width: 100, // 可选 立方体光墙宽度
    length: 50, // 可选 立方体光墙长度
    type: 'cube', //类型
    speed: -0.2 // 速度
})
// 省略 设置坐标position 以及 rotation 根据自己实际场景进行设置
 this.scene.add(wall)

6. 点光墙生成方法

js 复制代码
 'points': ({points,height,isClosed}) =>{
        const pointList = points.map(item => new Vector3(item.x, item.y, item.z));
       
        const positions = [];
        const uvs = [];
        const indices = [];

        let totalLength = 0;
        const loopCount = isClosed ? pointList.length : pointList.length - 1;
        for (let i = 0; i < loopCount; i++) {
            const current = pointList[i];
            const next = pointList[(i + 1) % pointList.length]; // 如果不闭合,最后一段不会取回起点
            if (!isClosed && i === loopCount - 1) break; // 不闭合时最后一次不计算
            totalLength += current.distanceTo(next);
        }

        let currentLength = 0;

        for (let i = 0; i < loopCount; i++) {
            const current = pointList[i];
            const next = isClosed ? pointList[(i + 1) % pointList.length] : pointList[i + 1];

            if (!next) break; // 不闭合时防止越界

            const segmentLength = current.distanceTo(next);
            //下边
            positions.push(
                current.x, current.y, current.z,
                next.x, next.y, next.z
            );
            // 上边
            positions.push(
                next.x, next.y + height, next.z,
                current.x, current.y + height, current.z
            );
            // uv
            const segmentUStart = currentLength / totalLength;
            const segmentUEnd = (currentLength + segmentLength) / totalLength;
            uvs.push(
                segmentUStart * 2, 0,
                segmentUEnd * 2, 0,
                segmentUEnd * 2, 1,
                segmentUStart * 2, 1
            );

            const vertexOffset = i * 4;
            indices.push(
                vertexOffset, vertexOffset + 1, vertexOffset + 2,
                vertexOffset, vertexOffset + 2, vertexOffset + 3
            );
            currentLength += segmentLength;
        }

        const geometry = new BufferGeometry();
        geometry.setAttribute('position', new Float32BufferAttribute(positions, 3));
        geometry.setAttribute('uv', new Float32BufferAttribute(uvs, 2));
        geometry.setIndex(indices);
        geometry.computeVertexNormals();
        return geometry;
  }

使用示例

js 复制代码
import { TextureLoader, SRGBColorSpace, RepeatWrapping } from 'three';
import imgLine from '@/assets/images/wall/vertical-line.png'
const textureLoader =  (url)=>{
      const textureLoader = new TextureLoader()
      const texture = textureLoader.load(url)
      texture.colorSpace = SRGBColorSpace;
      texture.wrapS  = RepeatWrapping;
      texture.wrapT  = RepeatWrapping;
      return texture
}
const wallImg =  utils.textureLoader(imgLine)
wallImg.repeat.set(10,2)
const wall = new useLightWall({
    map: wallImg,
    color: 0xf12711,
    opacity: 1.0,
    height: 40,  // 可选 光墙高度
    type: 'points', //类型
    points: [], 这里面是点位集合
    speed: -0.2 // 速度
})
// 省略 设置坐标position 以及 rotation 根据自己实际场景进行设置
 this.scene.add(wall)

7. 根据模型生成光墙

在实际项目场景中 刚开始我们 以当前场景模型为例

js 复制代码
const getBoxModel (name){
    // 假如 场景中地面模型的物体名称为 对象1186
     const model = scene.getObjectByName('对象1186')
     // 计算模型包围盒
     const boundingBox = new THREE.Box3().setFromObject(model);
     const boundingSphere = new THREE.Sphere()
     boundingBox.getBoundingSphere(boundingSphere)
        // 计算模型的长 宽 高
     const [leng,width,height] = [boundingBox.max.x - boundingBox.min.x,boundingBox.max.z - boundingBox.min.z,boundingBox.max.y - boundingBox.min.y]
     const diagonal = Math.sqrt(Math.sqrt(leng ** 2 + width ** 2) ** 2 + height ** 2); // 获取最长边的长度,
        return {
            width,
            height,
            leng,
            diagonal,
            boundingBox,
            center: boundingSphere.center
        }
 }
 const {center,length,width,height} = getBoxModel("对象1186")
 const wallHeight = 40;//光墙高度
 const borderWidth = 1; // borderWidth 目的是 左右两边 模型与光墙 之间稍微留点缝隙
 const borderLength = 2;// borderLength 目的是 前后两边 模型与光墙 之间稍微留点缝隙
 const wall = new useLightWall({
       map: wallImg,
       color: 0xf12711,
       opacity: 1.0,
       height: wallHeight,
       width: width + borderWidth,
       length: length + borderLength,
       type: 'cube',
       speed: -0.2
})
// 设置光墙的位置
wall.position.set(center.x - length / 2 - borderLength / 2, center.y  + wallHeight -  height / 2,center.z - width / 2 - borderWidth / 2)

可以根据不同的map 同时组合多种效果叠加

总结

useLightWall 类封装了光墙效果的核心逻辑,具备以下优点:

  • 高度灵活:支持三种几何体类型,满足大多数场景需求。
  • 易于扩展 :通过修改 geometry 函数,可轻松支持更多形状。
  • 性能可控 :使用 MeshBasicMaterial 避免光照计算,适合大规模使用。
相关推荐
朝与暮2 小时前
《javascript进阶-类(class):构造函数的语法糖》
前端·javascript
用户6120414922132 小时前
jsp+servlet做的咖啡品牌管理后台
java·前端·后端
Asort2 小时前
JavaScript设计模式(三)——抽象工厂模式 (Abstract Factory)
前端·javascript·设计模式
nyf_unknown3 小时前
(vue)前端下载本地excel文件
前端·vue.js·excel
fcm193 小时前
(6) tauri之前端框架性能对比
前端·javascript·rust·前端框架·vue·react
今晚务必早点睡3 小时前
前端缓存好还是后端缓存好?缓存方案实例直接用
前端·后端·缓存
IT_陈寒4 小时前
Vue3性能优化:5个被低估的Composition API技巧让我打包体积减少了40% 🚀
前端·人工智能·后端
x007xyz4 小时前
🚀🚀🚀前端的无限可能-纯Web实现的字幕视频工具 FlyCut Caption
前端·openai·音视频开发
前端Hardy4 小时前
HTML&CSS: 在线电子签名工具
前端·javascript·canvas