当前位置: 首页 > article >正文

threejs:用着色器给模型添加光带扫描效果

第一步:给模型添加光带

首先创建一个立方体,不进行任何缩放平移操作,也不要set position。

基础代码如下:

在顶点着色器代码里varying vec3 vPosition;vPosition = position;获得threejs自动计算的顶点坐标插值(也就是这个模型上每个点的xy坐标),然后在片元着色器代码里同样varying vec3 vPosition;来获取xy坐标值。

先设置整体颜色gl_FragColor = vec4(0.0,1.0,1.0,1.0);

然后再通过if条件判断,符合条件的片元设置其他颜色,光带就形成了。

gl_Position = projectionMatrix*viewMatrix*modelMatrix*vec4( position, 1.0 );是固定写法,你可以试下去掉会发生什么。

import * as THREE from 'three';const geometry = new THREE.BoxGeometry(30,60,30);const vertexShader = `varying vec3 vPosition;//表示顶点插值后位置数据,与片元数量相同,一一对应void main(){vPosition = position;// 顶点位置坐标插值计算// 投影矩阵 * 视图矩阵 * 模型矩阵 * 模型顶点坐标gl_Position = projectionMatrix*viewMatrix*modelMatrix*vec4( position, 1.0 );}
`;const fragmentShader = `varying vec3 vPosition;void main(){// 设置整体颜色gl_FragColor = vec4(0.0,1.0,1.0,1.0);// 当vPosition.y的位置符合if条件时,设置其他颜色,就会形成光带if(vPosition.y > 20.0 && vPosition.y < 22.0 ){gl_FragColor = vec4(1.0,1.0,0.0,1.0);}}
`;// 以下代码是使用着色器材料进行颜色设置
export const material = new THREE.ShaderMaterial({//顶点着色器对象vertexShadervertexShader: vertexShader,// 片元着色器对象fragmentShaderfragmentShader: fragmentShader});export const model = new THREE.Mesh(geometry, material);

在y坐标20的地方,有一条宽度为2的光带,因为限制条件是20<y<22。 

第二步:让光带动起来 

想要让光带动起来,只需要限制条件也动起来,比如之前的20<y<22,要让这两个数字随时间发生变化。这时候需要在shader材质里使用uniforms定义一个对象变量startY,包含value属性。

export const material = new THREE.ShaderMaterial({uniforms:{startY:{value:-30.0} //立方体位于原点,y的最小值是-30.0,而不是0.0},//顶点着色器对象vertexShadervertexShader: vertexShader,// 片元着色器对象fragmentShaderfragmentShader: fragmentShader});

在片元着色器里接收uniform里的变量,名字必须跟shader材质里定义的相同,注意这里是uniform,shader材质里是uniforms,将vPosition.y的范围限定在startY和startY+2.0之间。

const fragmentShader = `varying vec3 vPosition;uniform float startY;void main(){// 设置整体颜色,不然模型会设置为默认白色gl_FragColor = vec4(0.0,1.0,1.0,1.0);// 当vPosition.y的位置符合if条件时,设置其他颜色,就会形成光带if(vPosition.y > startY && vPosition.y < startY + 2.0 ){gl_FragColor = vec4(1.0,1.0,0.0,1.0);}}
`;

在渲染循环里让startY不断改变,片元着色器里的startY跟着变化,光带就动起来了。

特别注意,startY的起始值是-30.0,而不是0.0,startY的最大值是30.0,由于vPosition.y是浮点型数据,在对其进行计算的变量也必须是浮点型。

// 渲染循环
function render() {material.uniforms.startY.value += 0.5;// 当y超过模型高度后,y重置到模型底部if(material.uniforms.startY.value>30.0){material.uniforms.startY.value = -30.0;}renderer.render(scene, camera);requestAnimationFrame(render);
}

这时会发现光带移动到顶部的时候,会出现闪烁,我们把startY的值再缩小一点,避免这个问题。

之前是material.uniforms.startY.value>30.0 的时候,startY重置,改成material.uniforms.startY.value>25.0没这个现象了,具体上限是多少,跟模型高度和光带宽度有关,根据自己的实际项目来设置即可。

第三步:美化光带

光带的上半部分,从下往上,从光带颜色渐变到模型本身的颜色;下半部分,从上往下,从光带颜色渐变到模型本身的颜色。

const fragmentShader = `varying vec3 vPosition;uniform float startY;const float bandWidth = 20.0;//光带宽度float halfBandWidth = bandWidth*0.5;//光带宽度的一半const vec3 bandColor = vec3(1.0,0.0,0.0);//光带的颜色const vec3 baseColor = vec3(0.0,1.0,1.0);//模型本身的颜色void main(){// 设置整体颜色,不然模型会设置为默认白色gl_FragColor = vec4(baseColor,1.0);// 光带上半部分if(vPosition.y > startY && vPosition.y < startY + halfBandWidth ){float percent = (vPosition.y-startY)/halfBandWidth;//范围0~1gl_FragColor.rgb = mix( bandColor, baseColor, percent);}// 光带下半部分if(vPosition.y <= startY && vPosition.y > startY - halfBandWidth ){float percent = (startY - vPosition.y)/halfBandWidth;//范围0~1gl_FragColor.rgb = mix( bandColor, baseColor, percent);}}
`;

为了更方便看渐变色效果,先把光带移动停下了,光带加宽,光带设置为红色。

注意:gl_FragColor是vec4类型,表示片元颜色的rgba,gl_FragColor.rgb表示当前片元颜色的rgb,不带a(透明度)。

mix是着色器语言GLSL ES的内置函数,可以直接使用,比如参数1和2分表示一个颜色值,通过参数3百分比per,就可以控制两个颜色color1、color2的混合比例,参数3范围控制在0~1就行。

mix的参数1和2顺序,不用刻意记住,用代码测试下就行,不对就反过来。

mix的两个颜色参数,是vec3的,只包含rgb信息,所以只需要赋值给gl_FragColor.rgb即可,此时默认的透明度是1.0,如果确实需要设置a,可以写成gl_FragColor = vec4( mix( bandColor, baseColor, percent),0.8); 把vec3变成vec4.

要特别注意,光带颜色和模型颜色不要设置为一样,否则就会跟我一样,看不到光带,还以为是代码逻辑有问题,检查了好几遍才发现。

第四步:增加光带

想要显示多条移动光带,startY不仅要在渲染循环量不断变化,还要在for循环变化,直接写startY += float(i);会报错uniform里的变量不能修改,我们得换个方法,定义另一个uniform变量time,在片元着色器里将time赋值给另一个普通的float变量startY,再在for循环了来改变startY。

// 以下代码是使用着色器材料进行颜色设置
export const material = new THREE.ShaderMaterial({uniforms:{time:{value:0.0} //立方体位于原点,y的最小值是-30.0,而不是0.0},//顶点着色器对象vertexShadervertexShader: vertexShader,// 片元着色器对象fragmentShaderfragmentShader: fragmentShader});
const fragmentShader = `varying vec3 vPosition;uniform float time;const float bandWidth = 4.0;//光带宽度const float bandSpacing = 4.0;//光带间隔float halfBandWidth = bandWidth*0.5;//光带宽度的一半const vec3 bandColor = vec3(1.0,1.0,0.0);//光带的颜色const vec3 baseColor = vec3(0.0,1.0,1.0);//模型本身的颜色void main(){// 设置整体颜色,不然模型会设置为默认白色gl_FragColor = vec4(baseColor,1.0);float startY = -30.0+time; //-30.0是模型y坐标的起始值//循环产生多条光带for(int i=0;i<10;i++){startY += float(i)+bandSpacing;// 光带上半部分if(vPosition.y > startY && vPosition.y < startY + halfBandWidth ){float percent = (vPosition.y-startY)/halfBandWidth;//范围0~1gl_FragColor.rgb = mix( bandColor, baseColor, percent);}// 光带下半部分if(vPosition.y <= startY && vPosition.y > startY - halfBandWidth ){float percent = (startY - vPosition.y)/halfBandWidth;//范围0~1gl_FragColor.rgb = mix( bandColor, baseColor, percent);}}}
`;
function render() {material.uniforms.time.value += 0.5;// 当y超过模型高度后,y重置到模型底部if(material.uniforms.time.value>15.0){material.uniforms.time.value = 0.0;}renderer.render(scene, camera);requestAnimationFrame(render);
}

 

运行后看效果,顶部又开始了闪烁,需要将vPosition.y的值需要限制一下。

在if条件里加上vPosition.y <30.0,注意,光带的上半部分和下半部分都要加这句话。

我的模型里是小于30,具体小于多少,以自己的项目来调整。

旋转后发现底部也有闪烁,继续做限制vPosition.y >-30.0。

模型底部没有光带,需要将startY的下限继续下移。把-30改成-40,总之要比模型本身y的最小值更小。当time=0的时候,startY等于-40,比模型的底部更低,具体值多少,以自己的项目来调整。

float startY = -40.0+time; //模型y坐标的起始值。

除了调整以上数据,还可以调整time重置的条件,比如time大于10和大于20,效果是不同的。 

调整后的效果。

第五步:将光带换成彩色

创建光带颜色数组,在for循环里对数组长度循环取值即可。

换成彩色后,光带直接有一个很大的空隙,这是光带数组中有一个光带颜色跟模型本身的颜色一样,mix后就看不出来光带颜色了,这也是一个需要注意的地方,换个模型颜色后,间隔恢复正常了。

完整代码:

import * as THREE from 'three';const geometry = new THREE.BoxGeometry(30,60,30);const vertexShader = `varying vec3 vPosition;//表示顶点插值后位置数据,与片元数量相同,一一对应void main(){vPosition = position;// 顶点位置坐标插值计算// 投影矩阵 * 视图矩阵 * 模型矩阵 * 模型顶点坐标gl_Position = projectionMatrix*viewMatrix*modelMatrix*vec4( position, 1.0 );}
`;const fragmentShader = `varying vec3 vPosition;uniform float time;const float bandWidth = 4.0;//光带宽度const float bandSpacing = 4.0;//光带间隔float halfBandWidth = bandWidth*0.5;//光带宽度的一半//光带的颜色const vec3 bandColor[7] = vec3[7](vec3(1.0,0.0,0.0), vec3(1.0,0.5,0.0), vec3(1.0,1.0,0.0),vec3(0.0,1.0,0.0), vec3(0.0,1.0,1.0), vec3(0.0,0.0,1.0),vec3(1.0,0.0,1.0));const vec3 baseColor = vec3(1.0,1.0,1.0);//模型本身的颜色void main(){// 设置整体颜色,不然模型会设置为默认白色gl_FragColor = vec4(baseColor,1.0);float startY = -40.0+time; //模型y坐标的起始值float percent = 0.0;int colorIndex = 0;for(int i=0;i<10;i++){startY += float(i)+bandSpacing;colorIndex = int(mod(float(i),float(bandColor.length())));// 光带上半部分if(vPosition.y > startY && vPosition.y < startY + halfBandWidth && vPosition.y <30.0 && vPosition.y >-30.0){percent = (vPosition.y-startY)/halfBandWidth;//范围0~1gl_FragColor.rgb = mix( bandColor[colorIndex], baseColor, percent);}// 光带下半部分if(vPosition.y <= startY && vPosition.y > startY - halfBandWidth && vPosition.y <30.0 && vPosition.y >-30.0){percent = (startY - vPosition.y)/halfBandWidth;//范围0~1gl_FragColor.rgb = mix( bandColor[colorIndex], baseColor, percent);}}}
`;// 以下代码是使用着色器材料进行颜色设置
export const material = new THREE.ShaderMaterial({uniforms:{time:{value:0.0} //立方体位于原点,y的最小值是-30.0,而不是0.0},//顶点着色器对象vertexShadervertexShader: vertexShader,// 片元着色器对象fragmentShaderfragmentShader: fragmentShader});export const model = new THREE.Mesh(geometry, material);
// 渲染循环
function render() {material.uniforms.time.value += 0.5;if(material.uniforms.time.value>10.0){material.uniforms.time.value = 0.0;}renderer.render(scene, camera);requestAnimationFrame(render);
}

相关文章:

threejs:用着色器给模型添加光带扫描效果

第一步&#xff1a;给模型添加光带 首先创建一个立方体&#xff0c;不进行任何缩放平移操作&#xff0c;也不要set position。 基础代码如下&#xff1a; 在顶点着色器代码里varying vec3 vPosition;vPosition position;获得threejs自动计算的顶点坐标插值&#xff08;也就…...

1.从0搭建前端Vue项目工程

我们通过vue官方提供的脚手架Vue-cli来快速生成一个Vue的项目模板。 **注意&#xff1a;**需要先安装NodeJS&#xff0c;然后才能安装Vue-cli。 环境准备好了&#xff0c;接下来我们需要通过Vue-cli创建一个vue项目&#xff0c;然后再学习一下vue项目的目录结构。Vue-cli提供了…...

开放鸿蒙OpenHarmony 5.0.0 Release 兼容性测试实战经验分享

OpenHarmony 5.0版本的发布时间是2024年12月20日至21日。这个版本带来了许多新特性和改进。现在5.0出了两个release 版本&#xff0c;分别是5.0.0和5.0.1。 就在5.0版本发布不到2周的时间内&#xff0c;2025年01月01日起&#xff0c;不支持新产品基于老分支&#xff08;OpenHar…...

Chromium_src源码

Chromium_src源码 码云上有一个OpenHarmony-TPC/chromium_src项目&#xff0c;目前已经停止维护了&#xff0c;迁移到GitCode上了&#xff0c;源代码项目地址为&#xff1a;openharmony-tpc/chromium_chrome 特此记录一下老的项目的相关软件架构 Chromium 简介 软件架构 软…...

深度学习的正则化深入探讨

文章目录 一、说明二、学习目标三、什么是机器学习中的正则化四、了解过拟合和欠拟合五、代价函数的意义六、什么是偏差和方差&#xff1f;七、机器学习中的正则化&#xff1f; 一、说明 在训练机器学习模型时&#xff0c;模型很容易过拟合或欠拟合。为了避免这种情况&#xf…...

《OpenCV》——dlib(人脸应用实例)

文章目录 dlib库dlib库——人脸应用实例——表情识别dlib库——人脸应用实例——疲劳检测 dlib库 dlib库的基础用法介绍可以参考这篇文章&#xff1a;https://blog.csdn.net/lou0720/article/details/145968062?spm1011.2415.3001.5331&#xff0c;故此这篇文章只介绍dlib的人…...

tauri2+typescript+vue+vite+leaflet等的简单联合使用(一)

项目目标 主要的目的是学习tauri。 流程 1、搭建项目 2、简单的在项目使用leaflet 3、打包 准备项目 环境准备 废话不多说&#xff0c;直接开始 需要有准备能运行Rust的环境和Node&#xff0c;对于Rust可以参考下面这位大佬的文章&#xff0c;Node不必细说。 Rust 和…...

本地部署阿里万象2.1文生视频模型(Wan2.1-T2V)完全指南

在生成式AI技术爆发式发展的今天,阿里云开源的万象2.1(Wan2.1)视频生成模型,为创作者提供了从文字/图像到高清视频的一站式解决方案。本文针对消费级显卡用户,以RTX 4060 Ti 16G为例,详解本地部署全流程与性能调优方案,涵盖环境配置、多模型选择策略、显存优化技巧及实战…...

# [Linux] [Anaconda]解决在 WSL Ubuntu 中安装 Anaconda 报错问题

在 Windows 10 中安装了 WSL&#xff08;Windows Subsystem for Linux&#xff09;并使用 Ubuntu 后&#xff0c;你可能会下载 Anaconda 的 Linux 版本进行安装。但在安装过程中&#xff0c;可能会遇到 tar (child): bzip2: Cannot exec: No such file or directory 这样的错误…...

ES怎么查询大于10000条数据

在Elasticsearch&#xff08;ES&#xff09;中&#xff0c;默认情况下&#xff0c;查询结果的最大返回条数是10,000条。如果你需要查询超过10,000条数据&#xff0c;可以通过以下几种方式来实现&#xff1a; 1. 使用 scroll API scroll API 适用于需要处理大量数据的场景&…...

【Vue CLI脚手架开发】——3.组件交互props配置

文章目录 前言一、props数据接收方式二、代码实现1. 父组件2.子组件 三、分析 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 例如&#xff1a;随着人工智能的不断发展&#xff0c;机器学习这门技术也越来越重要&#xff0c;很多人都开启了学习机器学习…...

FPGA之USB通信实战:基于FX2芯片的Slave FIFO回环测试详解

FPGA之Usb数据传输 Usb 通信 你也许会有疑问&#xff0c;明明有这么多通信方式和数据传输&#xff08;SPI、I2C、UART、以太网&#xff09;为什么偏偏使用USB呢? 原因有很多&#xff0c;如下&#xff1a; 1. 高速数据传输能力 高带宽&#xff1a;USB接口提供了较高的数据传…...

【Office-Word】如何自动生成中英文目录

1.目录介绍 Word这个自动生成目录非常强大&#xff0c;涉及的功能很琐碎&#xff0c;想要完美的生成目录不仅仅是只会目录这么简单&#xff0c;前后涉及到的大纲级别、目标样式和域代码等操作是比较头疼的。 下面就一步一步开始介绍 2.多级标题级别编号设置 目录想要设置好…...

Oracle删除重复数据保留其中一条

Oracle删除重复数据保留其中一条 在Oracle数据库中&#xff0c;要删除重复数据并保留其中一条记录&#xff0c;可以使用多种方法。这里介绍两种常见的方法&#xff1a;使用ROWID或使用ROW_NUMBER()窗口函数。 方法1&#xff1a;使用ROWID ROWID是Oracle中用来唯一标识表中每…...

CentOS 7 安装Nginx-1.26.3

无论安装啥工具、首先认准了就是官网。Nginx Nginx官网下载安装包 Windows下载&#xff1a; http://nginx.org/download/nginx-1.26.3.zipLinxu下载 wget http://nginx.org/download/nginx-1.26.3.tar.gzLinux安装Nginx-1.26.3 安装之前先安装Nginx依赖包、自行选择 yum -y i…...

家政预约小程序用例图分析

在和客户进行需求沟通的时候&#xff0c;除了使用常规的问答的形式&#xff0c;我还使用图形化工具更深入的沟通。比如借助UML的用例图来开展系统分析&#xff0c;并且按照角色详细拆解了家政预约小程序的各个用例。在分析阶段思考的越多&#xff0c;沟通的越多&#xff0c;在系…...

112页精品PPT | DeepSeek行业应用实践报告

这份文件是一份关于DeepSeek行业应用实践的报告&#xff0c;以PPT形式呈现&#xff0c;共112页&#xff0c;详细介绍了DeepSeek及其核心产品DeepSeek-R1的技术特点、市场表现、应用路径以及在多领域的实践案例。报告展示了DeepSeek在市场上的快速崛起&#xff0c;包括其日活用户…...

计算机毕业设计SpringBoot+Vue.js航空机票预定系统(源码+文档+PPT+讲解)

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…...

C语言学习笔记-初阶(27)操作符详解1:位操作

1. 操作符的分类 上述的操作符&#xff0c;我们已经学过算术操作符、赋值操作符、逻辑操作符、条件操作符和部分的单目操作符&#xff0c;今天继续介绍⼀部分&#xff0c;操作符中有一些操作符和二进制有关系&#xff0c;我们先铺垫一下二进制的和进制转换的知识。 2. 二进制、…...

网络安全需要学多久才能入门?

网络安全是一个复杂且不断发展的领域&#xff0c;想要入行该领域&#xff0c;我们需要付出足够多的时间和精力好好学习相关知识&#xff0c;才可以获得一份不错的工作&#xff0c;那么网络安全需要学多久才能入门?我们通过这篇文章来了解一下。 学习网络安全的入门时间因个人的…...

20250304学习记录

第一部分&#xff0c;先来了解一下各种论文期刊吧&#xff0c;毕竟也是这把岁数了&#xff0c;还什么都不懂呢 国际期刊&#xff1a; EI收集的主要有两种&#xff0c; JA&#xff1a;EI源刊 CA&#xff1a;EI会议 CPCI也叫 ISTP 常说的SCI分区是指&#xff0c;JCR的一区、…...

【星云 Orbit • STM32F4】08. 用判断数据头来接收据的串口通用程序框架

【星云 Orbit • STM32F4】08. 用判断数据头来接收据的串口通用程序框架 1. 引言 本教程旨在帮助嵌入式开发小白从零开始&#xff0c;学习如何在STM32F407微控制器上实现一个基于串口的数据接收程序。该程序能够通过判断数据头来接收一串数据&#xff0c;并将其存储到缓冲区中…...

文件上传复现

文件上传漏洞的概念 在现代互联网的web应用程序中&#xff0c;上传文件是一种常见的功能&#xff0c;因为它有助于提高业务效率&#xff0c;比如社交 网站中&#xff0c;允许用户上传图片、视频、头像和许多其他类型的文件。然而向用户提供的功能越多&#xff0c; web应 用受到…...

Redis——缓存穿透、击穿、雪崩

缓存穿透 什么是缓存穿透 缓存穿透说简单点就是大量请求的 key 根本不存在于缓存中&#xff0c;导致请求直接到了数据库上&#xff0c;根本没有经过缓存这一层。举个例子&#xff1a;某个黑客故意制造我们缓存中不存在的 key 发起大量请求&#xff0c;导致大量请求落到数据库…...

HMC7043和HMC7044芯片配置使用

一,HMC7043芯片 MC7043独特的特性是对14个通道分别进行独立灵活的相位管理。所有14个通道均支持频率和相位调整。这些输出还可针对50 Ω或100 Ω内部和外部端接选项进行编程。HMC7043器件具有RF SYNC功能,支持确定性同步多个HMC7043器件,即确保所有时钟输出从同一时钟沿开始…...

STM32程序的加密与破解以及烧录方法

STM32程序的加密与破解&#xff0c;以及烧录方法。 盗取他人的PCB和烧录文件&#xff0c;可以节省大大开发成本&#xff0c;何乐而不为呢。因此&#xff0c;就滋生了一些协助他人盗版的公司。为了防止被盗版和复制&#xff0c;单片机工程师也是煞费苦心&#xff0c;对硬件和软…...

Redis和MySQL的实时数据同步方案

针对 Redis 和 MySQL 的实时数据同步&#xff0c;需根据业务场景选择不同的技术方案&#xff0c;核心目标是保障数据一致性、降低延迟、提升系统可靠性。以下是几种典型方案及其适用场景&#xff1a; 方案一&#xff1a;基于 MySQL Binlog 的异步同步 原理 监听 MySQL 的 Bin…...

VSCode知名主题带毒 安装量900万次

目前微软已经从 Visual Studio Marketplace 中删除非常流行的主题扩展 Material Theme Free 和 Material Theme Icons&#xff0c;微软称这些主题扩展包含恶意代码。 统计显示这些扩展程序的安装总次数近 900 万次&#xff0c;在微软实施删除后现在已安装这些扩展的开发者也会…...

JavaScript 进阶A(作用域、闭包、变量和函数提升、函数相关只是、数组解构、对象解构、构造函数

1.作用域 作用域主要分为&#xff1a;局部作用域和全局作用域。 局部作用域又分为&#xff1a;函数作用域和块作用域 函数作用域&#xff1a;在函数中定义的变量只能在函数内部使用&#xff0c;外部无法访问块作用域&#xff1a;被大括号{}包起来的代码块&#xff0c;在这个…...

mybatis映射文件相关的知识点总结

mybatis映射文件相关的知识点总结 mybatis官网地址 英文版&#xff1a;https://mybatis.org/mybatis-3/index.html 中文版&#xff1a;https://mybatis.p2hp.com/ 搭建环境 /* SQLyog Ultimate v10.00 Beta1 MySQL - 8.0.30 : Database - mybatis-label *****************…...