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,一经查实,立即删除!

相关文章

C++11之智能指针

C11之智能指针 前言1、智能指针概念2. 智能指针的定义和使用2.1 auto_ptr&#xff08;C11已经抛弃&#xff09;2.2 share_ptr2.3 unique_ptr2.4 weak_ptr 前言 C程序设计中&#xff0c;动态内存的管理式通过一对运算符来完成的&#xff1a;new和delete。 使用堆内存是非常频繁…

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

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

关于json.dumps()写入文件时是utf8

json.dumps()默认情况下&#xff0c;该函数会自动处理Unicode编码。 不要直接在json.dumps()设置encodingutf-8&#xff0c;会报错 json.dumps got an unexpected keyword argument encoding 需要将json.dumps()中设置ensure_asciiFalse&#xff0c;结合open函数中的encodin…

Java解析第三方接口返回的json

在实际开发过程中&#xff0c;免不了和其他公司进行联调&#xff0c;调用第三方接口&#xff0c;这个时候我们就需要根据对方返回的数据进行解析&#xff0c;获得我们想要的字段 第一种 //这种是data里面有个list的格式 {"data": {"username": "s…

linux 网络设备驱动之报文接收

从网络上接收报文比发送它要难一些, 因为必须分配一个 sk_buff 并从一个原子性上下 文中递交给上层. 网络驱动可以实现 2 种报文接收的模式: 中断驱动和查询. 大部分驱 动采用中断驱动技术, 这是我们首先要涉及的. 有些高带宽适配卡的驱动也可能采用查询 技术; 我们在"接收…

Kali Linux——aircrack-ng无线教程

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

java句柄数过多解决办法

java句柄数过多解决办法 使用file-leak-detector指定为应用程序的javaagent&#xff0c;然后重启程序&#xff0c;通过file-leak-detector提供能力&#xff0c;可以查看句柄和线程名称的信息&#xff0c;可直接排查出是哪行代码问题 包下载地址&#xff1a;http://search.mav…

项目整体管理

整体管理之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…

python函数装饰器保存信息

1 python函数装饰器保存信息 python函数装饰器&#xff0c;可以通过实例属性、全局变量、非局部变量和函数属性&#xff0c;来保存被装饰函数的状态信息。 1.1 统计调用并跟踪 描述 通过装饰器统计函数调用次数&#xff0c;并且用打印来跟踪调用记录。 此装饰器用类的__ca…

02 Singleton单例

抽丝剥茧设计模式 之 Singleton单例 - 更多内容请见 目录 文章目录 一、Singleton单例二、单例模式的八种实现1、饿汉式1Java实现go实现 2、饿汉式2Java实现go实现 3、懒汉式Java实现go实现 4、懒汉式-加锁Java实现go实现 5、懒汉式-缩小加锁代码块Java实现go实现 6、懒汉式-双…

FastDFS之快速入门、上手

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

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

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