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场景中,完全显示出所有尾气条带

相关推荐
cy_cy0028 小时前
互动滑轨屏如何优化参观动线?
科技·3d·人机交互·交互·软件构建
Coovally AI模型快速验证9 小时前
CVPR 2026|PanDA:首个多模态3D全景分割的无监督域适应框架
人工智能·3d·视觉检测·工业质检
AGV算法笔记13 小时前
CVPR 2024顶级SLAM论文精读:SplaTAM如何用3D高斯实现稠密RGB-D SLAM?
深度学习·3d·机器人视觉·slam·三维重建
hhhhhh_we15 小时前
皮肤人格的工程化实现:预颜美历如何用3D点云与循环神经网络构建数字孪生人格
图像处理·人工智能·rnn·深度学习·神经网络·3d·产品运营
Coovally AI模型快速验证15 小时前
YOLO26仓储检测实战:物体定位+有向边界框+姿态估计+实例分割,一个模型盯住整个仓库
大数据·人工智能·3d·视觉检测·工业质检
三维频道16 小时前
柔性材料3D数字化:蓝光扫描在内衣胸垫设计与质检中的应用
人工智能·3d·逆向工程·蓝光3d扫描仪·服装数字化·内衣设计·柔性材料检测
三维频道17 小时前
岩土力学微观探索:蓝光3D扫描在断面粗糙度分析中的应用
3d·新拓三维·xtom·蓝光3d扫描仪·岩土力学·结构面粗糙度·jrc
不知名的老吴18 小时前
渲染器Corona 11.2 for 3ds Max全流程下载与安装指南
3d
LateFrames1 天前
5 种 3D 模型文件格式比对( .asc / .stl / .obj / .ply / .3mf )
3d
dgaf1 天前
DX12 快速教程(17) —— 立体图标与合并渲染
c语言·c++·3d·图形渲染·d3d12