Skip to content

173. 实战:佛光普照(三)

Published:

上节画了佛光的粒子:

2025-08-10 10.43.35.gif

这节我们把后面的圆环画一下:

image.png

image.png

当然,这个没有标准,怎么画都行,我们综合一下。

和我们之前画的云雷纹差不多:

2025-08-09 23.35.36.gif

这只不过图案不一样。

创建 halo.js

import * as THREE from 'three';
import { Line2, LineGeometry, LineMaterial } from 'three/examples/jsm/Addons.js';
import { loadPromise } from './mesh';

const group = new THREE.Group();

const arc1 = new THREE.EllipseCurve(0, 0, 130 , 130, 0, Math.PI * 2);
const pointsArr1 = arc1.getPoints(50);
const geometry1 = new LineGeometry();
geometry1.setFromPoints(pointsArr1);
const material1 = new LineMaterial({
    color: new THREE.Color('gold'),
    linewidth: 10
});
const line1 = new Line2(geometry1, material1);
group.add(line1);

loadPromise.then((gltf) => {
    const box3 = new THREE.Box3();
    box3.expandByObject(gltf.scene);

    const size = box3.getSize(new THREE.Vector3());

    group.position.y = size.y * 2 / 3;
    group.position.z = -size.z / 2;
})

export default group;

先画一个大的圆环。

因为要设置 lineWidth,这里得用 LineGEometry、LineMaterial、Line2 的 api

然后等模型加载完,调到佛祖三分之二高的位置。

引入下看效果:

image.png

2025-08-10 11.10.18.gif

然后再画一圈线:

image.png

image.png

const geometry2 = new LineGeometry();
const pointsArr2 =  [];
for(let angle = 0; angle <= Math.PI * 2; angle += Math.PI  / 6) {
    pointsArr2.push(
        new THREE.Vector3(0, 0, 0),
        new THREE.Vector3(150 * Math.cos(angle), 150 * Math.sin(angle), 0),
    )
}
geometry2.setFromPoints(pointsArr2);
const material2 = new LineMaterial({
    color: new THREE.Color('white'),
    linewidth: 2
});
const line2 = new Line2(geometry2, material2);
group.add(line2);

这里要设置 LineWidth,所以也是用 Line2

image.png

然后多加几圈圆圈:

image.png

for(let i = 0; i<= 2; i++) {
    const arc = new THREE.EllipseCurve(0, 0, 110 + i * 20 , 110 + i * 20, 0, Math.PI * 2);
    const pointsArr = arc.getPoints(50);
    const geometry = new LineGeometry();
    geometry.setFromPoints(pointsArr);
    const material = new LineMaterial({
        color: new THREE.Color('gold'),
        linewidth: 3
    });
    const line = new Line2(geometry, material);
    group.add(line);
}

110、130、150 三个圆圈,宽度为 3

image.png

最后加一个最里面的圆:

image.png

const circleGeometry = new THREE.CircleGeometry(110);
const circleMaterial = new THREE.MeshBasicMaterial({
    color: '#faeb6c'
});
const circle = new THREE.Mesh(circleGeometry, circleMaterial);
circle.position.z = 1;
group.add(circle);

image.png

去掉包围盒和 AxesHelper

image.png

给这个光环加一个转动的动画:

用 gsap:

pnpm install --save-dev gsap

image.png

不断旋转,并且scale 在 1 到 1.2 之间循环往复,yoyo 是悠悠球那种循环往复的动画效果。

gsap.to(group.scale, {
    x: 1.2,
    y: 1.2,
    z: 1.2,
    duration: 2,
    repeat: -1,
    yoyo: true,
    ease: 'none'
});

gsap.to(group.rotation, {
    z: Math.PI  * 2,
    duration: 10,
    repeat: -1,
    ease: 'none'
});

2025-08-10 11.52.34.gif

然后加上一圈 卍,不然有点单调

这个要用 SpriteText

pnpm install --save three-spritetext

image.png

const figureGroup = new THREE.Group();
for(let angle = 0; angle <= Math.PI * 2; angle += Math.PI  / 30) {
    const figureText = new SpriteText('卍', 12);
    figureText.color = 'gold';
    figureText.strokeWidth = 1;
    figureText.strokeColor = 'white';
    figureText.position.set(160 * Math.cos(angle), 160 * Math.sin(angle), 0);
    figureGroup.add(figureText);
}
group.add(figureGroup);

再加两圈数量少点的:

image.png

数量、半径不一样,内圈那个开始角度也调一下,不然文字正好在线上

for(let angle = 0; angle <= Math.PI * 2; angle += Math.PI  / 15) {
    const figureText = new SpriteText('卍', 12);
    figureText.color = 'gold';
    figureText.strokeWidth = 1;
    figureText.strokeColor = 'white';
    figureText.position.set(140 * Math.cos(angle), 140 * Math.sin(angle), 0);
    figureGroup.add(figureText);
}
for(let angle = Math.PI / 10; angle <= Math.PI * 2; angle += Math.PI  / 6) {
    const figureText = new SpriteText('卍', 12);
    figureText.color = 'gold';
    figureText.strokeWidth = 1;
    figureText.strokeColor = 'white';
    figureText.position.set(120 * Math.cos(angle), 120 * Math.sin(angle), 0);
    figureGroup.add(figureText);
}

2025-08-10 12.06.42.gif

有点乱,去掉最外面一圈。

image.png

image.png

让它做反向运动:

gsap.to(figureGroup.rotation, {
    z: -Math.PI  * 2,
    duration: 5,
    repeat: -1,
    ease: 'none'
})

2025-08-10 12.10.30.gif

最后加一个透明度变化的动画,那种渐隐渐现的效果:

image.png

let obj = { opacity: 0.2 };
gsap.to(obj, {
    opacity: 1,
    duration: 2,
    repeat: -1,
    yoyo: true,
    ease: 'none',
    onUpdate() {
        group.traverse(o => {
            if(o.isMesh || o.isSprite) {
                o.material.transparent = true;
                o.material.opacity = obj.opacity;
            }
        })
    }
})

透明度从 0.2 到 1 变化,遍历所有对象修改透明度。

2025-08-10 12.10.30.gif

佛光的光线太长了,我们调整下轨迹的时间:

image.png

大小是 1 到 3,轨迹时长是 1 到 5s,一次 500 个

startSize: new IntervalValue(1, 3),
emissionOverTime: new ConstantValue(500),
rendererEmitterSettings: {
    startLength: new IntervalValue(1,5)
},

2025-08-10 12.29.01.gif

案例代码上传了小册仓库

总结

这节我们加上了外面的光环。

画设置 lineWidth 的线需要用 LineGeometry、LineMaterial、Line2 的 api

我们分别用 Line、Circle、SpriteText 画了一圈圈光环和文字。

最后用 gasp 做了 scale、opacity、rotation 的动画。

下节我们继续来加上底座的部分。

评论