threejs 光带扩散动画

发布时间:2024年01月10日

目录

一、创建光带

(1) 设置光带顶点

(2)??设置光带顶点透明度属性

二、光带动画

完整代码

html文件代码

?js文件代码

最后展示一下项目里的效果:


最近项目中要求做一段光带效果动画,尝试着写了一下,下面是本次分享光带扩散动画的效果预览:

20240110_204035

一、创建光带

(1) 设置光带顶点

这里使用缓冲区几何体bufferGeometry,通过设置顶点属性position来构成光带模型,在创建顶点之前需要一下几个必备参数:

r光带初始时的半径
h光带的高度
radian弧度值
segment间隔段数,光带由N段矩形构成(矩形由2个三角形构建),此属性决定矩形数量,值越大光带越接近圆形
interval每段间隔的弧度值
// 创建缓冲区几何体
const geometry = new THREE.BufferGeometry();
// 光带初始半径
const r = 10;
// 光带高度
const h = 10;
// 弧度
let radian = 0;
// 间隔段数,此值越高光带棱角越分明
const segment = 50;
// 弧度间隔
const interval = (Math.PI * 2) / segment;

接下来就是创建光带的顶点位置数组了,光带由N个矩形组成,一个矩形又由两个三角形构成;

for循环遍历间隔段数segment,每3个值代表一个顶点位置,3个顶点位置又组成一个三角形;

x轴上的位置使用Math.cos函数得出,z轴上的位置使用Math.sin函数得出,y轴则看三角形的三个点创建顺序来得出。此处我创建点位时,三角形底下的点为点2,所以点2的y值设置为0

第一个三角形点位顺序(第二个三角形类推,这里不展示了):

最后通过bufferAttribute属性设置几何体顶点位置,注意顶点位置数组需要转换成32位浮点类型的数组

// 顶点位置数组
const vertexPosArr = [];
// 遍历出光带的顶点数据
for (let i = 0; i < segment; i++) {
    // 弧度逐渐增加,从0度增加到360度
    radian += interval;
    // 计算出两个三角形的顶点位置,形成一个矩形平面,最后多个矩形平面组成圆形的光带
    vertexPosArr.push(
        // 第一个三角形
        Math.cos(radian) * r, h, Math.sin(radian) * r, // 点1
        Math.cos(radian) * r, 0, Math.sin(radian) * r, // 点2
        Math.cos(radian + interval) * r, 0, Math.sin(radian + interval) * r,  // 点3
        // 第二个三角形
        Math.cos(radian) * r, h, Math.sin(radian) * r, // 点1
        Math.cos(radian + interval) * r, 0, Math.sin(radian + interval) * r,  // 点2
        Math.cos(radian + interval) * r, h, Math.sin(radian + interval) * r,  // 点3
    )
}

// 设置几何体缓冲区position属性
geometry.attributes.position = new THREE.BufferAttribute(new Float32Array(vertexPosArr), 3);

(2)??设置光带顶点透明度属性

光带是渐变透明的,由黑到白(效果中蓝色是因为材质设置了蓝色将白色替换了);

通过获取顶点的getY函数获取当前顶点的y值(也就是顶点的高度),(1-顶点高度) / 光带高度使光带从下往上逐渐透明,也可以换成顶点高度 / 光带高度使光带从上往下逐渐透明

// 设置几何体缓冲区position属性
geometry.attributes.position = new THREE.BufferAttribute(new Float32Array(vertexPosArr), 3);
// 获取顶点
const position = geometry.attributes.position;
// 顶点总数量
const count = position.count;
// 透明度数组,每个顶点位置将会对应一个透明度
const alphaArr = [];
// 根据高度设置顶点透明度
for (let i = 0; i < count; i++) {
    alphaArr.push((1 - position.getY(i) / h));
}
// 设置几何体缓冲区alpha属性
geometry.attributes.alpha = new THREE.BufferAttribute(new Float32Array(alphaArr), 1);

(3)? 创建光带材质

这里使用的普通网格材质,这里必须设置side属性为THREE.DoubleSide双面可见、材质透明度transparent属性开启

至于材质使用onBeforeCompile函数替换着色器shader代码一块这里不做说明了,因为这一块东西很多,一时也说不清楚。

// 创建光带的材质
const material = new THREE.MeshBasicMaterial({
    color: '#00ffff',
    side: THREE.DoubleSide,
    transparent: true,
    depthTest: false,
})
// 材质渲染前所执行,替换shader着色器代码
material.onBeforeCompile = (shader) => {
    shader.vertexShader = shader.vertexShader.replace(
        'void main() {',
        `
            // 引进透明度分量
            attribute float alpha; 
            // varying声明一个属性,赋值透明度分量alpha,让片元着色器能拿到这个属性
            varying float vAlpha;
            void main() {
                vAlpha = alpha;
          `,
    )
    shader.fragmentShader = shader.fragmentShader.replace(
        'void main() {',
        `
            // 引进从顶点着色器传递的透明度分量
            varying float vAlpha;
            void main() {
          `,
    )
    shader.fragmentShader = shader.fragmentShader.replace(
        '#include <output_fragment>',
        `
            #include <output_fragment>
            // 设置颜色和透明度值,让光带有一个渐变效果
            gl_FragColor = vec4( outgoingLight, vAlpha  );
        `,
    )
}
const lightBand = new THREE.Mesh(geometry, material);
scene.add(lightBand);

二、光带动画

这里的光带动画是写在循环执行函数内的,有过threejs基础一定不陌生;

这里每次使用clone属性克隆光带模型获取scale的x值(换成z值也一样,y值不可以),通过if判断光带当前缩放大小来决定相应操作;

这里光带将从1倍扩散到9倍,7倍到9倍的时候会逐渐减小光带高度,这有就又了光带扩散动画末尾的逐渐消失效果,最后超过9倍重置缩放倍数为1,形成循环;

// 渲染循环
function render () {
    // 光带当前缩放倍数
    let scale = lightBand.clone().scale.x;
    // 小于7时scale不断增加
    if (scale < 7) {
        scale += 0.02;
        // 重新设置光带缩放倍数
        lightBand.scale.set(scale, 1, scale);
    } 
    // 小于8时scale不断增加,但是光带高度逐渐减小
    else if (scale < 9) {
        scale += 0.02;
        lightBand.scale.set(scale, (9 - scale) / 2, scale);
    } 
    // 大于9时光带缩放倍数重置为1
    else {
        scale = 0;
        lightBand.scale.set(scale, scale, scale);
    }
    renderer.render(scene, camera);
    requestAnimationFrame(render);
}
render();

完整代码

这里使用的html+js构建的小案例,threejs使用的148的版本

html文件代码

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<style>
    body {
        overflow: hidden;
        margin: 0;
    }
</style>

<body>
    <div id="webgl"></div>
    <script type="importmap">
        {
            "imports":{
                "three":"../../build/three.module.js",
                "three/addons/": "../../examples/jsm/"
            }
        }
    </script>
    <script src="./index.js" type="module"></script>
</body>

</html>

?js文件代码

import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

const width = window.innerWidth;
const height = window.innerHeight;

// 创建场景
const scene = new THREE.Scene();

// 设置光源
const pointLight = new THREE.PointLight('#ffffff', 1, 0);
pointLight.position.set(200, 0, 200);
scene.add(pointLight);
const ambientLight = new THREE.AmbientLight(0xffffff, 0.2);
scene.add(ambientLight);

// 创建透视相机
const camera = new THREE.PerspectiveCamera(30, width / height, 1, 3000);
camera.position.set(0, 0, 100);
camera.lookAt(0, 0, 0);

// 创建渲染器
const renderer = new THREE.WebGLRenderer({
    antialias: true,
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(width, height);
document.body.appendChild(renderer.domElement);

const planeGeometry = new THREE.PlaneGeometry(200, 200);
const planeMaterial = new THREE.MeshBasicMaterial({ color: '#696969' });
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.rotateX(-Math.PI / 2);
scene.add(plane);

// 创建缓冲区几何体
const geometry = new THREE.BufferGeometry();
// 光带初始半径
const r = 10;
// 光带高度
const h = 10;
// 弧度
let radian = 0;
// 间隔段数,此值越高光带棱角越分明
const segment = 50;
// 弧度间隔
const interval = (Math.PI * 2) / segment;
// 顶点位置数组
const vertexPosArr = [];
// 遍历出光带的顶点数据
for (let i = 0; i < segment; i++) {
    // 弧度逐渐增加,从0度增加到360度
    radian += interval;
    // 计算出两个三角形的顶点位置,形成一个矩形平面,最后多个矩形平面组成圆形的光带
    vertexPosArr.push(
        // 第一个三角形
        Math.cos(radian) * r, h, Math.sin(radian) * r, // 点1
        Math.cos(radian) * r, 0, Math.sin(radian) * r, // 点2
        Math.cos(radian + interval) * r, 0, Math.sin(radian + interval) * r,  // 点3
        // 第二个三角形
        Math.cos(radian) * r, h, Math.sin(radian) * r, // 点1
        Math.cos(radian + interval) * r, 0, Math.sin(radian + interval) * r,  // 点2
        Math.cos(radian + interval) * r, h, Math.sin(radian + interval) * r,  // 点3
    )
}

// 设置几何体缓冲区position属性
geometry.attributes.position = new THREE.BufferAttribute(new Float32Array(vertexPosArr), 3);
// 获取顶点数
const position = geometry.attributes.position;
// 顶点总数量
const count = position.count;
// 透明度数组,每个顶点位置将会对应一个透明度
const alphaArr = [];
// 根据高度设置顶点透明度
for (let i = 0; i < count; i++) {
    const temp = 1 - position.getY(i) / h;
    alphaArr.push(temp);
    console.log(temp, position.getY(i))
}
// 设置几何体缓冲区alpha属性
geometry.attributes.alpha = new THREE.BufferAttribute(new Float32Array(alphaArr), 1);
// 创建光带的材质
const material = new THREE.MeshBasicMaterial({
    color: '#00ffff',
    side: THREE.DoubleSide,
    transparent: true,
    depthTest: false,
})
// 材质渲染前所执行,替换shader着色器代码
material.onBeforeCompile = (shader) => {
    shader.vertexShader = shader.vertexShader.replace(
        'void main() {',
        `
            // 引进透明度分量
            attribute float alpha; 
            // varying声明一个属性,赋值透明度分量alpha,让片元着色器能拿到这个属性
            varying float vAlpha;
            void main() {
                vAlpha = alpha;
          `,
    )
    shader.fragmentShader = shader.fragmentShader.replace(
        'void main() {',
        `
            // 引进从顶点着色器传递的透明度分量
            varying float vAlpha;
            void main() {
          `,
    )
    shader.fragmentShader = shader.fragmentShader.replace(
        '#include <output_fragment>',
        `
            #include <output_fragment>
            // 设置颜色和透明度值,让光带有一个渐变效果
            gl_FragColor = vec4( outgoingLight, vAlpha  );
        `,
    )
}
const lightBand = new THREE.Mesh(geometry, material);
scene.add(lightBand);

// 渲染循环
function render () {
    // 光带当前缩放倍数
    let scale = lightBand.clone().scale.x;
    // 小于7时scale不断增加
    if (scale < 7) {
        scale += 0.02;
        // 重新设置光带缩放倍数
        lightBand.scale.set(scale, 1, scale);
    }
    // 小于8时scale不断增加,但是光带高度逐渐减小
    else if (scale < 9) {
        scale += 0.02;
        lightBand.scale.set(scale, (9 - scale) / 2, scale);
    }
    // 大于9时光带缩放倍数重置为1
    else {
        scale = 0;
        lightBand.scale.set(scale, scale, scale);
    }
    renderer.render(scene, camera);
    requestAnimationFrame(render);
}
render();

// 创建相机轨道控制器
const controls = new OrbitControls(camera, renderer.domElement);
controls.addEventListener('change', () => {
    renderer.render(scene, camera);
})

// 设置界面跟随窗口自适应
window.onresize = function () {
    renderer.setSize(window.innerWidth, window.innerHeight);
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
}

最后展示一下项目里的效果:

案例中如有不足的请补充,不懂的也可以问我,我知道的会尽力解答

文章来源:https://blog.csdn.net/weixin_60645637/article/details/135512685
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。