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, // 点1Math.cos(radian) * r, 0, Math.sin(radian) * r, // 点2Math.cos(radian + interval) * r, 0, Math.sin(radian + interval) * r,  // 点3// 第二个三角形Math.cos(radian) * r, h, Math.sin(radian) * r, // 点1Math.cos(radian + interval) * r, 0, Math.sin(radian + interval) * r,  // 点2Math.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时光带缩放倍数重置为1else {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, // 点1Math.cos(radian) * r, 0, Math.sin(radian) * r, // 点2Math.cos(radian + interval) * r, 0, Math.sin(radian + interval) * r,  // 点3// 第二个三角形Math.cos(radian) * r, h, Math.sin(radian) * r, // 点1Math.cos(radian + interval) * r, 0, Math.sin(radian + interval) * r,  // 点2Math.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时光带缩放倍数重置为1else {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();
}

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

代码随想录算法训练营第一天|数组理论基础、704二分查找、27移除元素

数组理论基础 一维数组 数组中的元素在内存空间中是连续的数组名与数组中第一个元素的地址相同&#xff08;一维数组&#xff09;数组的下标从0开始删除数组的元素其实是用后面的元素覆盖掉要删除的元素数组的长度不能改变 二维数组 二维数组是按照行存储的&#xff0c;也是…

Kali Linux——aircrack-ng无线教程

目录 一、准备 二、案例 1、连接usb无线网卡 2、查看网卡信息 3、开启网卡监听 4、扫描wifi信号 5、抓取握手包 6、强制断开连接 7、破解握手包 三、预防 一、准备 1、usb无线网卡&#xff08;笔记本也是需要用到&#xff09; 2、密码字典&#xff08;Kali 系统自带…

项目整体管理

整体管理之10大项目管理&#xff1a; 核心域&#xff1a;进度&#xff0c;成本&#xff0c;质量&#xff0c;范围 辅助域&#xff1a;风险&#xff0c;沟通&#xff0c;采购&#xff0c;人力资源&#xff0c;干系人 项目管理相关方&#xff1a; 招标&#xff1a;买方&#x…

Acrel-5000重点用能单位能耗在线监测系统的实际应用分析-安科瑞 蒋静

摘要&#xff1a;根据《重点用能节能办法》&#xff08;国家发展改革委等第七部委2018年15号令&#xff09;、《重点用能单位能耗在线监测系统推广建设工作方案》&#xff08;发改环资[2017]1711号&#xff09;和《关于加速推进重点用能单位能耗在线监测系统建设的通知》&#…

评估LLM在细胞数据上的实用性(1)-基本概述

基于LLM的基础模型在工业和科学领域都取得了重大进展。本报告通过八个与单细胞数据相关的下游任务的综合实验&#xff0c;评估了LLM在单细胞测序数据分析中的性能。通过将七种不同的单细胞LLM与特定任务下的baselines进行比较&#xff0c;结果发现单细胞LLMs在所有任务中可能并…

Js-基础语法(二)

运算符 赋值运算符 赋值运算符&#xff1a;对变量进行赋值的运算符 已经学过的赋值运算符&#xff1a; 将等号右边的值赋予给左边, 要求左边必须是一个容器 其他赋值运算符&#xff1a; - */% 使用这些运算符可以在对变量赋值时进行快速操作 一元运算符 众多的 JavaScrip…

固定翼仿真的切换

delta固定翼飞行器模型 接着这篇文章文章链接&#xff0c;我们对飞行器模型进行改进&#xff0c; 我们知道&#xff0c;我们打开仿真模型 gazebo --verbose zephyr_ardupilot_demo.world 我们注意这最后一个语句 <model name"zephyr_delta_wing_demo">//加载z…

图像分类任务的可视化脚本,生成类别json字典文件

1. 前言 之前的图像分类任务可视化&#xff0c;都是在train脚本里&#xff0c; 用torch中dataloader将图片和类别加载&#xff0c;然后利用matplotlib库进行可视化。 如这篇文章中&#xff1a;CNN 卷积神经网络对染色血液细胞分类(blood-cells) 在分类任务中&#xff0c;必定…

零基础学习数学建模——(一)什么是数学建模

本篇博客将详细介绍什么是数学建模。 文章目录 个人简介什么是数学建模&#xff08;一&#xff09;引例&#xff1a;高中数学里的简单线性规划问题数学建模的定义及用途数学建模的定义数学建模的用途 正确认识数学建模 个人简介 ​ 本人在本科阶段获得过国赛省一、mathorcup数…

ssm基于Web的汽车客运订票系统的设计与实现论文

毕业设计&#xff08;论文&#xff09; 汽车客运订票系统 姓 名 ______________________ 学 号 ______________________ 班 级 ______________________ 专 业 ______________________ 院 部 ______________________ 指导教师 ______________________ 年 月 日 目 录 目 录 …

Unity3d 实现直播功能(无需sdk接入)

Unity3d 实现直播功能 需要插件 :VideoCapture 插件地址(免费的就行) 原理:客户端通过 VideoCapture 插件实现推流nodejs视频流转服务进行转发,播放器实现rtmp拉流 废话不多说,直接上 CaptureSource我选择的是屏幕录制,也可以是其他源 CaptureType选择LIVE–直播形式 LiveSt…

FastDFS之快速入门、上手

知识概念 分布式文件系统 通过计算机网络将各个物理存储资源连接起来。通过分布式文件系统&#xff0c;将网络上任意资源以逻辑上的树形结构展现&#xff0c;让用户访问网络上的共享文件更见简便。 文件存储的变迁&#xff1a; 直连存储&#xff1a;直接连接与存储&#xf…

websocket介绍并模拟股票数据推流

Websockt概念 Websockt是一种网络通信协议&#xff0c;允许客户端和服务器双向通信。最大的特点就是允许服务器主动推送数据给客户端&#xff0c;比如股票数据在客户端实时更新&#xff0c;就能利用websocket。 Websockt和http协议一样&#xff0c;并不是设置在linux内核中&a…

代码随想录算法训练营Day20 | 40.组合总和||、39.组合总和、131.分割回文串

LeetCode 40 组合总和|| 本题思路&#xff1a;由于解集中不能包含重复的组合&#xff0c;所以要进行去重的操作。 首先要将数组先进行一个排序操作然后在树层进行去重操作&#xff01;&#xff08;不懂的可以去看代码随想录讲解视频&#xff09;利用一个 used 数组来表示&…

全链路压力测试有哪些主要作用

全链路压力测试是在软件开发和维护过程中不可或缺的一环&#xff0c;尤其在复杂系统和高并发场景下显得尤为重要。下面将详细介绍全链路压力测试的主要作用。 一、全链路压力测试概述 全链路压力测试是指对软件系统的全部组件(包括前端、后端、数据库、网络、中间件等)在高负载…

解决 ubuntu 下编译文件的时候与 YAML 相关的的报错

输入&#xff1a; catkin build -DCMAKE_C_COMPILERgcc-8 -DCMAKE_CXX_COMPILERg-8 或 catkin build airsim_tutorial_pkgs -DCMAKE_C_COMPILERgcc-8 -DCMAKE_CXX_COMPILERg-8 报错如下&#xff1a; 可能是缺少 yaml-cpp 文件&#xff0c;然后操作&#xff1a; sudo apt-g…

书生·浦语大模型实战2

轻松玩转书生浦语大模型趣味 Demo 大模型及 InternLM 模型简介 什么是大模型 大模型通常指的是机器学习或人工智能领域中参数数量巨大、拥有庞大计算能力和参数规模的模型。这些模型利用大量数据进行训练&#xff0c;并且拥有数十亿甚至数千亿个参数。大模型的出现和发展得益…

单片机原理及应用——C51语言版(第2版,林立、张俊亮编著)课后习题及答案

第一章习题 1.1 单项选择题 &#xff08;1&#xff09; 单片机又称为单片微计算机&#xff0c;最初的英文缩写是____。 答案(D) A.MCPB.CPUC.DPJD.SCM &#xff08;2&#xff09; Intel公司的MCS-51系列单片机是______的单片机。 答案(C) A.1位B.4位C.8位D.16位 &#xf…

66.网游逆向分析与插件开发-角色数据的获取-角色类的数据分析与C++还原

内容来源于&#xff1a;易道云信息技术研究院VIP课 ReClass.NET工具下载&#xff0c;它下方链接里的 逆向工具.zip 里的reclass目录下&#xff1a;注意它分x64、x32版本&#xff0c;启动是用管理员权限启动否则附加时有些进程附加不上 链接&#xff1a;https://pan.baidu.com/…

【S32K 进阶之旅】 NXP S32K3 以太网 RMII 接口调试(2)

前言 前文介绍了 NXP S32K3 以太网 RMII 接口调试的开发环境搭建&#xff0c;下面开始详解软件调试步骤。没看过第一节的小伙伴请移步《【S32K 进阶之旅】 NXP S32K3 以太网 RMII 接口调试&#xff08;1&#xff09;》&#xff0c;话不多说我们直接进入正题。 lwip Stack 介绍 …