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

devtools/2025/3/7 0:25:36/

第一步:给模型添加光带

首先创建一个立方体,不进行任何缩放平移操作,也不要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 );是固定写法,你可以试下去掉会发生什么。

javascript">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属性。

javascript">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之间。

javascript">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是浮点型数据,在对其进行计算的变量也必须是浮点型。

javascript">// 渲染循环
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没这个现象了,具体上限是多少,跟模型高度和光带宽度有关,根据自己的实际项目来设置即可。

第三步:美化光带

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

javascript">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。

javascript">// 以下代码是使用着色器材料进行颜色设置
export const material = new THREE.ShaderMaterial({uniforms:{time:{value:0.0} //立方体位于原点,y的最小值是-30.0,而不是0.0},//顶点着色器对象vertexShadervertexShader: vertexShader,// 片元着色器对象fragmentShaderfragmentShader: fragmentShader});
javascript">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);}}}
`;
javascript">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后就看不出来光带颜色了,这也是一个需要注意的地方,换个模型颜色后,间隔恢复正常了。

完整代码:

javascript">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);
javascript">// 渲染循环
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.ppmy.cn/devtools/165110.html

相关文章

【AI大模型】DeepSeek + Kimi 高效制作PPT实战详解

目录 一、前言 二、传统 PPT 制作问题 2.1 传统方式制作 PPT 2.2 AI 大模型辅助制作 PPT 2.3 适用场景对比分析 2.4 最佳实践与推荐 三、DeepSeek Kimi 高效制作PPT操作实践 3.1 Kimi 简介 3.2 DeepSeek Kimi 制作PPT优势 3.2.1 DeepSeek 优势 3.2.2 Kimi 制作PPT优…

CentOS7快速安装minio

MinIO 是一款高性能、分布式的对象存储系统&#xff0c;它兼容 Amazon S3 API&#xff0c;特别为存储大量的非结构化数据而设计。非结构化数据指的是那些没有固定格式或模型的数据&#xff0c;如图片、视频、音频文件等。 以下是 MinIO 的一些关键特性和用途&#xff1a; 主要…

大数据技术基于聚类分析的消费者细分与推荐系统

标题:大数据技术基于聚类分析的消费者细分与推荐系统 内容:1.摘要 随着互联网和信息技术的飞速发展&#xff0c;企业面临着海量的消费者数据。如何从这些数据中提取有价值的信息&#xff0c;实现精准的消费者细分和个性化推荐&#xff0c;成为企业提升竞争力的关键。本研究旨在…

开篇词 | Go 项目开发极速入门课介绍

欢迎加入我的训练营&#xff1a;云原生 AI 实战营&#xff0c;一个助力 Go 开发者在 AI 时代建立技术竞争力的实战营。实战营中包含大量 Go、云原生、AI Infra 相关的优质实战课程和项目。欢迎关注我的公众号&#xff1a;令飞编程&#xff0c;持续分享 Go、云原生、AI Infra 技…

取消请求:axios.

axios.CancelToken和isCancel cancelToken的作用是获取取消函数&#xff0c;用来手动取消接口。 axios.isCancel的作用是在处理错误的时候判断当前错误&#xff0c;是否是由于取消导致的。 使用方式1 const testFun async () > {let cancel: any; // 保存取消函数// 发送…

AI数据分析:deepseek生成SQL

在当今数据驱动的时代&#xff0c;数据分析已成为企业和个人决策的重要工具。随着人工智能技术的快速发展&#xff0c;AI 驱动的数据分析工具正在改变我们处理和分析数据的方式。本文将着重介绍如何使用 DeepSeek 进行自动补全SQL 查询语句。 我们都知道&#xff0c;SQL 查询语…

编程题 - 明明的随机数【JavaScript/Node.js解法】

“千里之行&#xff0c;始于足下。” —— 老子 目录 明明的随机数 题目&#xff1a;js代码解答&#xff1a;通过&#xff1a; 明明的随机数 题目&#xff1a; 对于明明生成的 n 个 1 到 500 之间的随机整数&#xff0c;你需要帮助他完成以下任务&#xff1a; 删去重复的数字…

C语言:51单片机 结构体系(带最小系统设计图)

注&#xff1a;本案例采用AT89C51系列单片机 单片机的内核 89系列单片机型号由3个部分组成&#xff0c;分别是前缀、型号、后缀&#xff0c;格式为AT89C(LV\、S)XXXX。 前缀 由字母组成&#xff0c;表示某某公司的产品。 型号 型号由89 CXXXX或89 LVXXXX或89 SXXXX等表示…