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

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

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

相关文章

【时序预测】在线学习:算法选择(从线性模型到深度学习解析)

——如何为动态时序预测匹配最佳增量学习策略&#xff1f; 引言&#xff1a;在线学习的核心价值与挑战 在动态时序预测场景中&#xff08;如实时交通预测、能源消耗监控&#xff09;&#xff0c;数据以流式&#xff08;Streaming&#xff09;形式持续生成&#xff0c;且潜在的…

Spring Boot如何利用Twilio Verify 发送验证码短信?

Twilio提供了一个名为 Twilio Verify 的服务&#xff0c;专门用于处理验证码的发送和验证。这是一个更为简化和安全的解决方案&#xff0c;适合需要用户身份验证的应用。 使用Twilio Verify服务的步骤 以下是如何在Spring Boot中集成Twilio Verify服务的步骤&#xff1a; 1.…

【Linux操作系统】VM17虚拟机安装Ubuntu22.04,图文详细记录

1.双击桌面的 VMware Workstation17 Player&#xff0c;点击“创建新虚拟机”&#xff0c;如下图所示。 2.选择“稍后安装操作系统”&#xff0c;点击“下一步”。如下图所示。 3.客户机操作系统选择“Linux”&#xff0c;版本选择“ Ubuntu 64位”&#xff0c;然后点击“下一…

软件工程---净室软件工程

净室软件工程是一种软件开发方法&#xff0c;旨在通过形式化的数据和严格的测试来提高软件的可靠性和减少缺陷的数量。它的核心思想是在软件开发过程中最小化或消除软件缺陷&#xff0c;从而提高软件的质量和可靠性。这种方法强调在软件生命周期的早期阶段使用形式化方法进行规…

迷你世界脚本区域接口:Area

区域接口&#xff1a;Area 彼得兔 更新时间: 2023-12-18 11:35:14 具体函数名及描述如下: 序号 函数名 函数描述 1 createAreaRect(...) 创建矩形区域 2 createAreaRectByRange(...) 创建矩形区域(通过范围) 3 destroyArea(...) 销毁区域 4 getAre…

C# 牵手DeepSeek:打造本地AI超能力

一、引言 在人工智能飞速发展的当下&#xff0c;大语言模型如 DeepSeek 正掀起新一轮的技术变革浪潮&#xff0c;为自然语言处理领域带来了诸多创新应用。随着数据隐私和安全意识的提升&#xff0c;以及对模型部署灵活性的追求&#xff0c;本地部署 DeepSeek 成为众多开发者和…

Linux--基础命令3

大家好&#xff0c;今天我们继续学习Linux的基础命令 mv命令 mv命令是move的缩写&#xff0c;可以用来移动文件或者将文件改名 move(rename) files&#xff0c;经常⽤来备份⽂件或者目录 语法: mv [ 选项 ] 源⽂件或目录 目标⽂件或目录 mv src[文件、目录] dst[路径、文…

【每日八股】计算机网络篇(三):IP

目录 DNS 查询服务器的基本流程DNS 采用 TCP 还是 UDP&#xff0c;为什么&#xff1f;默认使用 UDP 的原因需要使用 TCP 的场景&#xff1f;总结 DNS 劫持是什么&#xff1f;解决办法&#xff1f;浏览器输入一个 URL 到显示器显示的过程&#xff1f;URL 解析TCP 连接HTTP 请求页…

探究DeepSeek R1与OpenAI模型文本相似度背后的秘密

摘要 一项由Copyleaks进行的新研究显示&#xff0c;DeepSeek R1生成的文本在风格上与OpenAI模型的相似度高达74.2%。这一发现引发了对DeepSeek训练数据来源和独特性的质疑。Copyleaks作为专业检测文本抄袭和AI生成内容的平台&#xff0c;其研究结果具有重要参考价值。此相似度揭…

【实战 ES】实战 Elasticsearch:快速上手与深度实践-2.2.3案例:电商订单日志每秒10万条写入优化

&#x1f449; 点击关注不迷路 &#x1f449; 点击关注不迷路 &#x1f449; 点击关注不迷路 文章大纲 Elasticsearch批量写入性能调优实战&#xff1a;2.2.3 案例&#xff1a;电商订单日志每秒10万条写入优化1. 原始架构与瓶颈分析1.1 初始集群配置1.2 性能瓶颈定位 2. 全链路…

统计Excel列中某值出现的次数

统计Excel列中某值出现的次数&#xff1a; 1、COUNTIF 函数用于计算满足特定条件的单元格数量。假设要统计 A 列中值为 “苹果” 出现的次数&#xff0c;在其他单元格中输入公式&#xff1a;COUNTIF(A:A,“苹果”)。其中&#xff0c;A:A表示要统计的范围是 A 列&#xff0c;&q…

nnUNet报错

nnUNet报错处理 Traceback (most recent call last):File "/opt/conda/envs/nnunet/lib/python3.11/threading.py", line 1045, in _bootstrap_innerself.run()File "/opt/conda/envs/nnunet/lib/python3.11/threading.py", line 982, in runself._target…

Compose Multiplatform+Kotlin Multiplatfrom 第四弹跨平台

文章目录 引言功能效果开发准备依赖使用gradle依赖库MVIFlow设计富文本显示 总结 引言 Compose Multiplatformkotlin Multiplatfrom 今天已经到compose v1.7.3&#xff0c;从界面UI框架上实战开发看&#xff0c;很多api都去掉实验性注解&#xff0c;表示稳定使用了&#xff01;…

(十一)基于vue3+mapbox-GL实现模拟高德实时导航轨迹播放

要在 Vue 3 项目中结合 Mapbox GL 实现类似高德地图的实时导航轨迹功能,您可以按照以下步骤进行: 安装依赖: 首先,安装 mapbox-gl 和 @turf/turf 这两个必要的库: npm install mapbox-gl @turf/turf引入 Mapbox GL: 在组件中引入 mapbox-gl 并初始化地图实例: <templ…

智慧园区大数据云平台建设总体方案,平台方案架构-智慧园区大数据平台(320页原件Word)

第一章 项目建设背景及现状 1.1. 项目建设背景 1.2. 项目建设必要性 1.3. 项目建设目标 1.4. 建设原则 第二章 园区创新发展趋势 2.1园区经济向生态型转变 2.2 园区企业向高新型转变 2.3园区管理向城市化转变 第三章 工业园区大数据存在的问题 3.1信息化配套设施及服…

NeurIPS24 Oral!多模态融合+目标检测全新里程碑!

最近发现多模态融合目标检测实在太热了&#xff01;顶会频出&#xff01;像是NeurIPS24 Oral上端到端算法E2E-MFD&#xff1b;ECCV24上性能提升30.8&#xff05;的FRN&#xff1b;TPAMI24上推理效率狂飙270&#xff05;倍的FSF…… 主要在于&#xff1a;一方面&#xff0c;其能…

网络编程——http

在Linux系统中使用C语言实现HTTP客户端或服务器通常涉及使用套接字编程和一些HTTP协议的基本知识。下面是一个简单的示例&#xff0c;展示了如何用C语言实现一个HTTP客户端&#xff0c;向一个HTTP服务器发送请求并接收响应。 1. HTTP客户端示例 (C语言) 这个例子展示了如何用C…

【Go语言快速上手】第一部分:数据类型(数组、切片、映射)与控制语句

文章目录 一、复合类型Ⅰ 数组1. 语法2. 示例3. 特点4. 数组的传递 Ⅱ 切片1. 定义2. 语法3. 示例4. 特点5. 切片的创建6. 切片的操作切片的扩展切片的拷贝 Ⅲ 映射1. 定义2. 语法3. 示例4. 特点5. 映射的创建6. 映射的操作示例&#xff1a;插入、访问和删除判断键是否存在示例…

EasyDSS视频推拉流/直播点播平台:Mysql数据库接口报错502处理方法

视频推拉流/视频直播点播EasyDSS互联网直播平台支持一站式的上传、转码、直播、回放、嵌入、分享功能&#xff0c;具有多屏播放、自由组合、接口丰富等特点。平台可以为用户提供专业、稳定的直播推流、转码、分发和播放服务&#xff0c;全面满足超低延迟、超高画质、超大并发访…