Cocos3.8版本 实现跟随3d物体的条带拖尾

实现类似汽车2条尾气的效果

1.使用粒子特效Trail Module 会导致在高速情况下,粒子断层,尾部颗粒感严重,而且粒子过多会导致draw call负担

2.使用2D拖尾 MotionStreak,会在轨道扭曲的时候,有显示层级或者重叠在一起的问题

(1)应该显示4条,实际看到了1条,是因为角度问题重叠了

(2)会在模型背侧显示出来,

3.最终实现,选择Mesh渲染,+材质

1.创建材质,渐变图+builtin材质
2.实际作用的脚本,代码如下,新建一个空节点,增加Mesh Renderer组件,无需设置mesh,把刚才的材质绑定,然后把脚本绑定上,调整脚本参数,设置要跟随的target,代码中设置的是两条拖尾,left和right,可以根据需求自己修改。主要就是通过路径绘制网格渲染出来。
复制代码
import {
    _decorator,
    Component,
    Vec3,
    MeshRenderer,
    utils,
    Node,
    find,
    Camera,
    isValid,
} from 'cc';

const { ccclass, property } = _decorator;

@ccclass('Trail3D')
export class Trail3D extends Component {

    @property({ type: Node })
    target: Node = null!; // 车
    @property(Camera)
    camera: Camera = null!;

    @property
    maxPoints = 80;

    @property
    width = 0.3;

    @property
    minDistance = 0.05;

    @property
    maxLength = 5; // 拖尾最大长度(米)

    private _points: Vec3[] = [];
    private _meshRenderer: MeshRenderer = null!;

    private _lastWorldPos: Vec3 = new Vec3();

    private isLeft: boolean = false;

    start() {
        this._meshRenderer = this.getComponent(MeshRenderer)!;

        if (!this.target) {
            console.error('Trail3D: target 未设置');
            return;
        }

        this._lastWorldPos.set(this.target.worldPosition);
    }

    setTarget(target: Node, left = false) {
        this.isLeft = left;
        this.target = target;
        if (left) {
            this._lastWorldPos.set(this.target.worldPosition.x, this.target.worldPosition.y + .4, this.target.worldPosition.z + 0.5);
        }
        else {
            this._lastWorldPos.set(this.target.worldPosition.x, this.target.worldPosition.y + .4, this.target.worldPosition.z - 0.5);
        }
    }

    update() {
        if (!this.target || isValid(this.target) == false) {
            this.node.destroy();
            return;
        }

        let currentWorld = this.target.worldPosition.clone();
        if (this.isLeft) {
            currentWorld.set(this.target.worldPosition.x, this.target.worldPosition.y + .4, this.target.worldPosition.z + 0.5);
        }
        else {
            currentWorld.set(this.target.worldPosition.x, this.target.worldPosition.y + .4, this.target.worldPosition.z - 0.5);
        }

        // ===== 不动就不更新(优化)=====
        if (Vec3.distance(this._lastWorldPos, currentWorld) < 0.0001) {
            return;
        }

        // ===== 距离补点(核心,解决断层)=====
        const dist = Vec3.distance(this._lastWorldPos, currentWorld);

        if (dist > this.minDistance) {
            const count = Math.floor(dist / this.minDistance);

            for (let i = 1; i <= count; i++) {
                const t = i / count;

                const pos = new Vec3();
                Vec3.lerp(pos, this._lastWorldPos, currentWorld, t);

                // ✅ 直接存世界坐标(关键)
                this._points.push(pos);
            }

            this._lastWorldPos.set(currentWorld);
        }

        // ===== 按长度裁剪(核心,防止变圆)=====
        let total = 0;
        for (let i = this._points.length - 1; i > 0; i--) {
            total += Vec3.distance(this._points[i], this._points[i - 1]);

            if (total > this.maxLength) {
                this._points.splice(0, i);
                break;
            }
        }

        // ===== 最大点限制(保险)=====
        if (this._points.length > this.maxPoints) {
            this._points.shift();
        }

        this._updateMesh();
    }

    private _updateMesh() {
        if (this._points.length < 2) return;

        const positions: number[] = [];
        const uvs: number[] = [];
        const indices: number[] = [];

        const right = new Vec3();
        const dir = new Vec3();
        const up = new Vec3(0, 1, 0);

        for (let i = 0; i < this._points.length; i++) {
            const p = this._points[i];

            // ===== 方向 =====
            if (i === 0) {
                Vec3.subtract(dir, this._points[i + 1], p);
            } else {
                Vec3.subtract(dir, p, this._points[i - 1]);
            }
            Vec3.normalize(dir, dir);

            // ===== 侧方向(稳定,不翻转)=====
            const camForward = this.camera.node.forward;

            Vec3.cross(right, dir, camForward);

            if (right.lengthSqr() < 0.0001) {
                right.set(1, 0, 0);
            }

            Vec3.normalize(right, right);

            // ===== 宽度渐变 =====
            const t = i / this._points.length;
            const w = this.width * t;

            const left = new Vec3();
            const rightPos = new Vec3();

            Vec3.scaleAndAdd(left, p, right, -w);
            Vec3.scaleAndAdd(rightPos, p, right, w);

            positions.push(left.x, left.y, left.z);
            positions.push(rightPos.x, rightPos.y, rightPos.z);

            // UV 用于透明渐变
            uvs.push(t, 0);
            uvs.push(t, 1);
        }

        // ===== 索引 =====
        for (let i = 0; i < this._points.length - 1; i++) {
            const i0 = i * 2;
            const i1 = i * 2 + 1;
            const i2 = i * 2 + 2;
            const i3 = i * 2 + 3;

            indices.push(i0, i2, i1);
            indices.push(i1, i2, i3);
        }

        this._rebuildMesh(positions, uvs, indices);
    }

    private _rebuildMesh(positions: number[], uvs: number[], indices: number[]) {

        const geometry = {
            positions,
            uvs,
            indices,
        };

        // ⚠️ Cocos 3.8 推荐
        const mesh = utils.MeshUtils.createMesh(geometry);

        this._meshRenderer.mesh = mesh;
    }
}

最终效果可以在3d场景中,完全显示出所有尾气条带

相关推荐
PhotonixBay1 天前
3D共聚焦显微镜与探针式粗糙度仪:谁更适合亚表面损伤检测?
图像处理·人工智能·测试工具·3d
自动化监测Learner2 天前
Cesium 加载大雁塔 3D Tiles 实录:从模型结构剖析到局部虚拟化(剖切 + 骨架线)实现
3d
FOORIR 客流统计2 天前
智能客流系统未来趋势:3D视觉、AI识别与数据分析的融合架构
人工智能·3d·数据分析
3D小将2 天前
3D格式转换之Rhino转换为IGS格式技术
3d·stp模型·igs模型·glb模型
PhotonixBay2 天前
InGaAs微透镜3D形貌怎么测?共聚焦显微镜在光通信的应用实践
功能测试·测试工具·3d·测试标准
学习星球3 天前
AI 一句话生成 3D 游戏世界:腾讯 HY-World 2.0 开源深度解析与本地实战
开发语言·人工智能·游戏·3d·ai·课程设计·ai编程
ai小陈3 天前
Hunyuan3D-2云端部署实战:图生3D、文生3D怎么跑更稳
人工智能·科技·3d·ai·音视频·gpu算力
独立开发之道3 天前
Three.js 创建 VR 内容:四步把一个普通 3D 场景送进头显
javascript·3d·vr
k4m7v2pz4 天前
Bevy 0.18.1 雨雪天气粒子系统排障实录:四个“想当然“如何让 3D/2D 粒子全部不可见
3d·游戏开发·3d渲染·bevy·天气粒子·2d渲染
戴西软件4 天前
戴西iDWS.3DViz Suite数据轻量化可视化软件,从传统桌面软件向云端协同的重大突破
大数据·运维·网络·人工智能·机器学习·3d