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.hqwc.cn/news/338006.html

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

相关文章

【JAVA】Java 中 Set集合常用方法

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a; JAVA ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 前言 正文 常用方法 代码示例 结语 我的其他博客 前言 Java中的Set接口提供了一种不允许包含重复元素的集合。常用的实现类有HashS…

小H靶场笔记:DC-9

DC-9 January 10, 2024 10:02 AM Tags&#xff1a;knockd Owner&#xff1a;只惠摸鱼 信息收集 使用arp-scan和nmap扫描C段存活主机&#xff0c;探测靶机ip&#xff1a;192.168.199.139&#xff0c;只有80端口开放。 再扫一下靶机端口服务的情况吧。发现22端口是被过滤的状…

探索Shadowsocks-Android:保护你的网络隐私

探索Shadowsocks-Android&#xff1a;保护你的网络隐私 I. 引言 在数字时代&#xff0c;网络隐私和安全变得愈发重要。我们越来越依赖互联网&#xff0c;但同时也面临着各种网络限制和监控。在这个背景下&#xff0c;Shadowsocks-Android应用程序应运而生&#xff0c;为用户提…

centos安装tomcat

前言 安装tomcat的话&#xff0c;应该没啥难度&#xff0c;只要是java装的没问题&#xff0c;直接拉上去都行 安装java方案之前已经给了&#xff1a; Linux/centos安装python、java、php等编译环境_linux服务器存在java环境安装php环境-CSDN博客 安装方式 1、下载 Apache Tom…

市场复盘总结 20240110

仅用于记录当天的市场情况&#xff0c;用于统计交易策略的适用情况&#xff0c;以便程序回测 短线核心&#xff1a;不参与任何级别的调整&#xff0c;采用龙空龙模式 昨日主题投资 连板进级率 33% 二进三&#xff1a; 进级率低 50% 最常用的二种方法&#xff1a; 方法一&…

创建型模式 | 建造者模式

一、建造者模式 1、原理 建造者模式又叫生成器模式&#xff0c;是一种对象的构建模式。它可以将复杂对象的建造过程抽象出来&#xff0c;使这个抽象过程的不同实现方法可以构造出不同表现&#xff08;属性&#xff09;的对象。创建者模式是一步一步创建一个复杂的对象&#xf…

IP地址的网络安全防护和预防

网络安全对于保护个人和组织的信息资产至关重要&#xff0c;而IP地址是网络通信的基础。在这篇文章中&#xff0c;IP数据云将探讨IP地址的网络安全防护和预防措施&#xff0c;以确保网络的安全性和可靠性。 IP地址是互联网上每个设备在网络中的唯一标识符。有IPv4和IPv6两种类…

【大数据OLAP引擎】StarRocks为什么快?

StarRocks的优势 StarRocks最初主要的优势是性能&#xff0c;当时在单表查询方面与性能标杆ClickHouse不相上下&#xff0c;而join优化特性使其在多表关联查询场景下的性能表现要远远优于ClickHouse&#xff0c;替换ClickHouse自然也就成了StarRocks的第一个目标。 而StarRoc…

CSS基础笔记-05layout

CSS基础笔记系列 《CSS基础笔记-01CSS概述》《CSS基础笔记-02动画》《CSS基础笔记-03选择器》《CSS基础笔记-04cascade-specificity-inheritance》 文章目录 CSS基础笔记系列什么是CSS布局布局方法normal flowflexboxgridfloats 总结 什么是CSS布局 CSS布局是指在页面中对元素…

前端中什么是DOM对象

DOM&#xff08;文档对象模型&#xff09;是一种编程接口&#xff0c;用于HTML和XML文档。它提供了一种将文档结构表示为树结构的方式&#xff0c;这使得程序和脚本能够动态地访问和更新文档的内容、结构和样式。 在前端开发中&#xff0c;DOM是非常重要的概念。当浏览器加载网…

websocket: 了解并利用nodejs实现webSocket前后端通信

目录 第一章 前言 1.1 起源 1.2 短轮询与长轮询 1.2.1 短轮询 1.2.2 长轮询 1.2.3 长连接&#xff08;SSE&#xff09; 1.2.4 websocket 第二章 利用Node以及ws创建webSocket服务器 2.1 创建ws服务器&#xff08;后端部分&#xff09; 2.1.1 了解一下 2.1.2 代创建W…

Pytorch从零开始实战16

Pytorch从零开始实战——ResNeXt-50算法的思考 本系列来源于365天深度学习训练营 原作者K同学 对于上次ResNeXt-50算法&#xff0c;我们同样有基于TensorFlow的实现。具体代码如下。 引入头文件 import numpy as np from tensorflow.keras.preprocessing.image import Ima…