threejs——车辆雷达智能识别效果


theme: fancy

highlight: atelier-seaside-light

前言

随着智能电车越来越火,3d车辆的官网效果也越来越盛行,趁着空档,写一个车辆雷达识别概念效果, 话不多说,直接上源码 https://gitee.com/sunhuapeng/automotive-radar

请点赞关注加分享,上车出发

灵感图

灵感图.jpg

效果图

2024-06-13 18.33.59.gif

技术栈

  • three.js 0.157.0;
  • nodejs v18.19.0
  • vite 4.3.2

实现思路

背景图

由于这是网上找的素材,所以背景图没有源文件,就自己画了一个差不多的,给大家推荐一个工具 在线psphotopea,快捷键、界面和photoshop没有什么区别,简单的画个图还可以。

加载背景图

```ts const createGround = () => {

const textureLoader = new THREE.TextureLoader();

const material = new THREE.MeshPhongMaterial({
    color: 0xFFFFFF,
    transparent: true,
    opacity: 1
})
ground = new THREE.Mesh(new THREE.BoxGeometry(18, 0, 640, 1, 1, 1), material);

textureLoader.load(`${import.meta.env.VITE_ASSETS_URL}/assets/images/背景图.png`, function (texture) {
    texture.wrapS = THREE.RepeatWrapping;
    texture.wrapT = THREE.RepeatWrapping;
    // 背景图重复次数
    texture.repeat.set(1, 12);
    ground.material.map = texture;
    ground.material.needsUpdate = true;
    ground.rotation.copy(new THREE.Euler(-Math.PI, -Math.PI * 0.5, 0))
    scene.add(ground)
});

} ```

背景图效果

背景图.jpg

这里需要对场景做一下修改,远处渐隐的效果使用的是scene.fog,这样看起来远处的道路不会很突兀。

.fog : Fog

一个fog实例定义了影响场景中的每个物体的雾的类型。默认值为null。

加载汽车模型

``ts const gltf = await loadGltf(${import.meta.env.VITEASSETSURL}/assets/models/car/scene.gltf`); const model = gltf.scene; const playerScale = 0.6

model.scale.set(playerScale, playerScale, playerScale) playerGroup.add(model) scene.add(playerGroup); ```

汽车模型.jpg

雷达波动

从第一张效果图中可以看出来,雷达扩散的效果,其实就是一个扇形,有不同的半径,不同的弧度,半透明的扇形和外侧白色的线,由此我们就可以写一个方法,创建这个完整的扇形,利用到的api有CircleGeometryLine2,方法接受两个参数,一个半径radius: number一个中心角thetaLength: number,首先创建出扇形,再通过扇形的定点,同步创建出lin2。Line2的所有顶点信息都从扇形获取,这样就能保证两个模型是同步的,再将线添加到扇形中。

```ts const createRadar = (radius: number, thetaLength: number) => { // 创建圆弧信息 const geometry = new THREE.CircleGeometry(radius, 361, -thetaLength / 2, thetaLength)

const material = new THREE.MeshBasicMaterial({
    color: 0xffffff,
    side: THREE.DoubleSide,
    transparent: true,
    opacity: 0.5,
});
// 创建扇形几何体
const circle = new THREE.Mesh(geometry, material);
circle.rotation.set(Math.PI * 0.5, 0, 0);

// 获取扇形几何体的顶点信息
const position = geometry.getAttribute('position');
const { count } = position;

const linePoints = []
// 循环几何体的顶点信息并加入到linePoints中。
for (let i = 1; i < count; i++) {
    const v3 = new THREE.Vector3().fromBufferAttribute(position, i);
    linePoints.push(...v3.toArray())
}
// 创建线
const line2Geometry = new LineGeometry();

// 设置line2的顶点信息
line2Geometry.setPositions(linePoints);

// 线的材质
const matLine = new LineMaterial({
    linewidth: 0.002, // 可以调整线宽
    dashed: true,
    opacity: 1,
    color: 0xffffff,
    vertexColors: false, // 是否使用顶点颜色
});

let line2 = new Line2(line2Geometry, matLine);
circle.add(line2)
scene.add(circle)

} ```

position顶点信息.jpg

有一点需要说明一下,扇形的顶点信息第一组0,0,0是中心点,而Line2是不需要的,所以在for循环的时候通过i=1来规避第一个点。createRadar(4, Math.PI*2)调用一下

Math.PI * 2Math.PI * 0.4的效果。

扇形1.jpg

扇形2.jpg

下面是结合以上所有元素创建的一个静态效果:

静态阶段.jpg

在开发过程中遇到一个问题,扇形外侧的白色线调整是否受scene.fog影响时,ts报了个错,结果我以为不支持,看了源码才发现问题,

line2属性没有fov.jpg

源码中constructor(parameters?: LineMaterialParameters);并没有这个属性,但是它继承了ShaderMaterial,这里是有fog属性的,虽然ts报错,但是效果是有的,当然也可以通过其他手段解决这个报错,还是要经常阅读源码,不然有一些奇奇怪怪的错误,都不晓得哪来的\~

3d内容的静态元素到此开发完毕,接下来是动态,给雷达添加扩散效果,并在道路上添加双向车辆,用于检测,

雷达扩散效果

前文createRadar方法接受两个参数 一个是半径,一个是中心角,动态扩散效果,就是利用这两个属性值的变化做出动态扩散效果,

修改一下createRadar方法,添加一个更新方法,方法新增type动画类型,用于区别从那个属性做动画,可选值"radius" | "thetaLength" | undefined,创建雷达,初始化属性InitialThetaLengthInitialRadiue,作为最基础的创建图形,方法实参radiusthetaLength作为补间动画的数值变化。当typeundefined时,半径和中心角同时变化。

ts ** * * @param radius 半径 * @param thetaLength 中心角 * @param index 索引,防止粘黏 * @param type 动画类型 */ const createRadar = (radius: number, thetaLength: number, index?: number, type?: "radius" | "thetaLength") => { // 初始化数据 const InitialThetaLength = Math.PI * 0.01; const InitialRadiue = 0.001; ...

THREE.BufferGeometryLineGeometry都可以通过设置顶点信息去更改图形,所以我们可以利用这个原理做一个半径和中心角的补间动画

```ts // 动态计算时间,让动画有点层次感 const tweenTime = (type === 'radius' ? 2 : thetaLength) * 1000;

// 初始值为半径和中心角的初始化值 new TWEEN.Tween({ radius: InitialRadiue, thetaLength: InitialThetaLength }) .to({ radius, thetaLength }, tweenTime) // 目标值为方法的实参radius和thetaLength .delay(1000 * 5 - tweenTime) // 停滞几秒后执行 .start() // 动画开始 .onUpdate(({ radius: r, thetaLength: t }) => { let newGeometry: THREE.CircleGeometry if (type === 'radius') { // 只对半径做修改 newGeometry = new THREE.CircleGeometry(r, 361, -thetaLength / 2, thetaLength); } else if (type === 'thetaLength') { // 只对中心角做修改 newGeometry = new THREE.CircleGeometry(radius, 361, -t / 2, t); } else { // 同时修改 newGeometry = new THREE.CircleGeometry(r, 361, -t / 2, t); }

// 更新雷达扇形
    circle.geometry.setAttribute('position', newGeometry.getAttribute('position'))

    // 更新扇形外围弧线顶点信息
    const linePoints = geometryAttribute2Array(newGeometry)
    line2Geometry.setPositions(linePoints);
})
.onComplete(() => {
    // 补间动画停止后的回调
})

```

通过修改值的不同,动态判断补间动画时长和停滞时长,让动画有一些层次。

接下来改一下调用方法

ts // 同时修改 createRadar(42, Math.PI * 0.04, 1) // 接近半圆的用中心角进行补间动画 createRadar(23, Math.PI * 0.4, 2, 'thetaLength') // 完整的圆对半径进行补间动画, createRadar(10, Math.PI * 2, 3, 'radius')

为了方便演示,我在代码里加了一个按钮,

雷达动画.gif

其他车辆

主角的雷达写完了,接下来该添加其他车辆,添加到道路上,[0, -1.8, -3.8, 4.4, 6.3, 8.2]提前准备一下这个数组,这个数字是根据车道的具体位置计算的,目的是为了让所有的车辆都能够保持在车道的中央,

```ts let ZP = [0, -1.8, -3.8, 4.4, 6.3, 8.2]

const size = otherCarModel.userData.size

// 添加多个其他车辆 for (let i = 0; i < 10; i++) { const OC = otherCarModel.clone();

const x = getRandomIntegerInRange(-10, 0)
const z = ZP[getRandomIntegerInRange(0, ZP.length - 1)]
if (z <= 0) {
    OC.rotation.set(0, -Math.PI * 0.5, 0)
}
const group = new THREE.Group()
group.add(OC)
// 随机分布
group.position.copy(new THREE.Vector3(x - i * size.x, 0, z))
yoyoTweene(OC)
otherCarGroup.add(group)

} ```

创建了10个其他车辆,随机分布在道路上,从代码中可以看到有一个yoyoTweene方法,这是一个让车辆做前后往复运动的补间动画,目的是为了模拟车辆运动的时候速度不均匀的效果,下面就该说让所有的车辆都开起来了

车辆运动

ts const yoyoTweene = (mesh: THREE.Object3D) => { const x = getRandomIntegerInRange(-3, 3) return new TWEEN.Tween({ x }) .to({ x: 3 }, 3000) // 目标值为方法的实参radius和thetaLength .yoyo(true) .repeat(Infinity) .start() .onUpdate(({ x }) => { if (mesh) { mesh.position.setX(x) } }) }

从代码中可以看到,汽车模型只是在小距离的往复运动,其实就是原地运动,这时我们就需要一个概念相对运动,控制每一个车辆的向前运动,改变模型的位置矩阵,想想都麻烦,而且一直往前走,x值多少是个头儿,所以这里开动一下发财的小脑瓜,让道路运动,车不动,那就达到一种效果,好像镜头一直跟踪车辆模型向前观察,而道路和两边的人物一直是向后退的,想象一下自己在车里的感觉,

好,那么好,接下来改造一下ground

```ts // 背景图重复次数 ... texture.repeat.set(1, 2);

// 设置贴图偏移值
    texture.offset.set(0, 0.12); // 这里的值可以根据你需要的偏移来调整

    const material = new THREE.MeshLambertMaterial({
        map: texture,
    })

... ```

这个是之前加载道路贴图的代码,修改一下offset值,就会发现贴图位置被改变了,通过这个api我们就可以得出以下的结论,那么92号混凝土对意大利面的影响因子有哪些?

```ts const loopGround = () => { new TWEEN.Tween({ offsetX: 0.12 }) .to({ offsetX: 1.12 }, 3000) .start() .repeat(Infinity) .onUpdate(({ offsetX }) => { ground.material.map.offset.set(0, offsetX); // 这里的值可以根据你需要的偏移来调整 }) }

```

通过上面的改造,就有了下面的效果,请看vcr

车辆运动.gif

发现一个问题没,哈哈哈对向车道的车一直在倒车\~

留个小坑,大家可以自己尝试解决。

历史文章

three.js——商场楼宇室内导航系统 内附源码

three.js——可视化高级涡轮效果+警报效果 内附源码

高德地图巡航功能 内附源码

three.js——3d塔防游戏 内附源码

three.js+物理引擎——跨越障碍的汽车 可操作 可演示

百度地图——如何计算地球任意两点之间距离 内附源码

threejs——可视化地球可操作可定位

three.js 专栏

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

孙华鹏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值