threejs 光带扩散动画

目录

一、创建光带

(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();
}

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/309017.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Kubernetes实战(十五)-Pod垂直自动伸缩VPA实战

1 介绍 VPA 全称 Vertical Pod Autoscaler&#xff0c;即垂直 Pod 自动扩缩容&#xff0c;它根据容器资源使用率自动设置 CPU 和 内存 的requests&#xff0c;从而允许在节点上进行适当的调度&#xff0c;以便为每个 Pod 提供适当的资源。 它既可以缩小过度请求资源的容器&…

STM32 使用 DS18B20 温度传感器实现环境温度监测

为了实现环境温度监测系统&#xff0c;我们可以利用STM32微控制器和DS18B20数字温度传感器。在本文中&#xff0c;我们将介绍如何通过STM32微控制器读取DS18B20传感器的温度数据&#xff0c;并展示一个简单的示例代码。 1. 系统概述 环境温度监测系统旨在使用DS18B20数字温度…

PolarDB DDL MDL

PolarDB DDL MDL 转载数据库内核那些事&#xff5c;深度解析PolarDB DDL锁的优化和演进 - 知乎 (zhihu.com) 概述 Request lock问题 MySQL 拿锁 即乐观等待的方式来拿锁MDL-X。问题&#xff1a;会导致DDL后续DML的阻塞。 第三方插件 pt-osc / gh-ost 采用copying method&…

微机原理常考简答题总结

一&#xff0c;8086和8088这两个微处理器在结构上有什么异同&#xff1f; &#xff08;1&#xff09;共同点&#xff1a;内部均由EU、BIU组成&#xff0c;结构基本相同&#xff1b;寄存器等功能部件均为16位&#xff1b;内部数据通路为16位&#xff1b;指令系统相同。 &#x…

搜维尔科技:【简报】元宇宙数字人赛道,2022年金奖《金魚姬》赏析!

一名网络直播主名叫琉璃&#xff0c;在即将展开她日常进行的每日准时直播前&#xff0c;肚子极为不舒服&#xff0c;突然很想上厕所&#xff0c;由于时间紧迫&#xff0c;导致琉璃需要在厕所里面完成直播&#xff01;为了掩饰自己所在的处境&#xff0c;她决定运用自己设计的虚…

vivado 工程管理

管理项目 打开项目 当项目打开时&#xff0c;Vivado IDE会从项目已关闭。项目状态包括当前源文件顺序、已禁用和已启用 源文件、活动约束文件和目标约束文件&#xff0c;以及合成、模拟和实现运行。要打开项目&#xff0c;请使用以下方法之一&#xff1a; •在“入门”页面…

聚道云软件连接器助力某家居公司实现付款流程自动化

客户介绍&#xff1a; 某家居公司是一家集家居研发、生产、销售于一体的综合性家居企业。公司业务遍布全国多个城市&#xff0c;拥有庞大的供应商网络和采购需求。 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 客户痛点&#xff1a; 付款申请单需要…

linux --proc文件夹学习笔记

内容在飞书文档&#xff1a; Docshttps://r0dhfl3ujy9.feishu.cn/docx/Xe2wd23MToSmGrxUm9kcVHrPn7g?fromfrom_copylink

mercury靶机

文章妙语 不与伪君子争名&#xff0c;不与真小人争利&#xff0c;不与执拗人争理&#xff0c;不与匹夫争勇&#xff0c;不与酸儒争才。不与蠢人施恩 一、信息收集 主机探测 端口探测 探测主机详细版本信息 8080开了http服务 目录扫描 robots.txt目录下什么也没有 二&#xff0…

lvs+keepalived+nginx双主模式双主热备实现负载均衡

目录 一、原理 二、真实服务器nginx配置 三、lvs的keepalived配置 3.1 配置文件 3.2 开启keepalived服务 四、测试 4.1 测试访问VIP 4.2 模拟lvs01宕机 主机名IPnginx0111.0.1.31nginx0111.0.1.31lvs0111.0.1.33lvs0211.0.1.34VIP111.0.1.29VIP211.0.1.30 一、原理 lvskeepal…

推理证明-条件等价式、德摩根律、双条件

对于命题逻辑部分来说&#xff0c;只需要掌握命题的符号化&#xff0c;以及如何进行推理证明即可。足矣。其他的都是一些基本的概念&#xff0c;扫一遍&#xff0c;记住即可。 对于什么是命题&#xff1a;陈述句、能判断、真值唯一 进行推理证明&#xff0c;我们需要记住以下…

查看服务器的yum 源

1、cd /etc/yum.repos.d 2、编辑 CentOS-Stream-Sources.repo 3、 查看里面的yum源地址 4、更新yum源&#xff0c;执行下面指令 yum clean all # 清除系统所有的yum缓存 yum makeacache # 生成新的yum缓存 yum repolist

TCN 时序卷积网络 (temporal convolutional network)【因果卷积、空洞卷积】

文章目录 TCN 时序卷积 &#xff08;temporal convolutional network&#xff09;1.因果卷积2.膨胀卷积 TCN 时序卷积 &#xff08;temporal convolutional network&#xff09; 它由膨胀卷积核因果卷积两种卷积构成。 如图&#xff1a;左边是膨胀因果卷积&#xff0c;右边是…

Python从入门到网络爬虫(23个Python开源项目)

前言 随着互联网的快速发展&#xff0c;大量的信息被不断地产生和积累&#xff0c;这也使得网络爬虫变得越来越重要。而Python作为一门高效、易用的编程语言&#xff0c;被广泛地应用于网络爬虫领域。本文将从多个角度分析Python开源爬虫项目的优缺点、应用场景以及未来发展方…

构建安全可靠的系统:第十六章到第二十章

第四部分&#xff1a;维护系统 原文&#xff1a;Part IV. Maintaining Systems 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 准备应对不舒适情况的组织有更好的机会处理关键事件。 尽管不可能为可能扰乱您组织的每种情况制定计划&#xff0c;但作为综合灾难规划策略…

作业--day44

完善对话框&#xff0c;点击登录对话框&#xff0c;如果账号和密码匹配&#xff0c;则弹出信息对话框&#xff0c;给出提示”登录成功“&#xff0c;提供一个Ok按钮&#xff0c;用户点击Ok后&#xff0c;关闭登录界面&#xff0c;跳转到其他界面。如果账号和密码不匹配&#xf…

C语言结构体的字节对齐

C语言结构体的字节对齐 什么是字节对齐 首先来看下面的程序&#xff1a; #include <stdio.h>typedef struct n1{int a;char b;char c; } N_stru1;typedef struct n2{char b;int a;char c; } N_stru2;int main() {N_stru1 n1;N_stru2 n2;printf("%d\n", siz…

MySQL从0到1全教程【1】MySQL数据库的基本概念以及MySQL8.0版本的部署

1 MySQL数据库的相关概念 1.1 数据库中的专业术语 1.1.1 数据库 (DB) 数据库是指:保存有组织的数据的容器(通常是一个文数据库 (database)件或一组文件)。 1.1.2 数据库管理系统 (DBMS) 数据库管理系统(DBMS)又称为数据库软件(产品)&#xff0c;用于管理DB中的数据 注意:…

【MySQL】视图,15道常见面试题---含考核思路详细讲解

目录 一 视图 1.1视图是什么 1.2 创建视图 1.3 查看视图(两种) 1.4 修改视图(两种) 1.5 删除视图 二 外连接&内连接&子查询介绍 2.1 外连接 2.2 内连接 2.3 子查询 三 外连接&内连接&子查询案例 3.1 了解表结构与数据 3.2 15道常见面试题 四 思…

道路拆除的题解

目录 原题描述&#xff1a; 题目描述 输入格式 输出格式 样例 #1 样例输入 #1 样例输出 #1 样例 #2 样例输入 #2 样例输出 #2 提示 题目大意&#xff1a; 主要思路&#xff1a; 至于dis怎么求&#xff1f; 代码code&#xff1a; 原题描述&#xff1a; 题目描述 …