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({
	//顶点着色器对象vertexShader
	vertexShader: vertexShader,
	// 片元着色器对象fragmentShader
	fragmentShader: 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
	},
	//顶点着色器对象vertexShader
	vertexShader: vertexShader,
	// 片元着色器对象fragmentShader
	fragmentShader: 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~1
      		gl_FragColor.rgb = mix( bandColor, baseColor, percent);
		}
		// 光带下半部分
		if(vPosition.y <= startY && vPosition.y > startY - halfBandWidth ){
			float percent = (startY - vPosition.y)/halfBandWidth;//范围0~1
      		gl_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
	},
	//顶点着色器对象vertexShader
	vertexShader: vertexShader,
	// 片元着色器对象fragmentShader
	fragmentShader: 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~1
      		gl_FragColor.rgb = mix( bandColor, baseColor, percent);
			}
			// 光带下半部分
			if(vPosition.y <= startY && vPosition.y > startY - halfBandWidth ){
			float percent = (startY - vPosition.y)/halfBandWidth;//范围0~1
      		gl_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~1
      		gl_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~1
			gl_FragColor.rgb = mix( bandColor[colorIndex], baseColor, percent);
			}
		}
	}
`;

// 以下代码是使用着色器材料进行颜色设置
export const material = new THREE.ShaderMaterial({
	uniforms:{
		time:{value:0.0} //立方体位于原点,y的最小值是-30.0,而不是0.0
	},
	//顶点着色器对象vertexShader
	vertexShader: vertexShader,
	// 片元着色器对象fragmentShader
	fragmentShader: 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);
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数据可携带权的多重价值与实践思考

文章目录 前言一、数据可携带权的提出与立法二、数据可携带权的多重价值1、推动数据要素市场化配置2、促进市场竞争与创新3、强化个人数据权益 三、数据可携带权的实践挑战1、数据安全与隐私保护面临风险2、接口差异导致数据迁移成本高昂3、可携带的数据范围尚存争议 数据可携带…

博客系统自动化测试_测试报告

项目背景 开发出一个专属于自己的博客系统&#xff0c;可以完全按照自己的需求来设计&#xff0c;比如&#xff1a;去掉那些花里胡哨的功能和广告&#xff0c;只保留自己真正需要的&#xff0c;比如文章发布、分类、标签、搜索等。可以根据自己的审美设计界面&#xff0c;想要…

【VUE】第一期——初使用、基本语法

目录 0 前言 1 准备工作 1.1 创建vue实例 1.2 vue开发者工具 2 插值表达式 2.1 基本用法 3 常用指令 3.1 内容渲染指令 3.1.1 v-text 3.1.2 v-html 3.2 条件渲染指令 3.2.1 v-show 3.2.2 v-if 3.2.3 v-else 和 v-else-if 3.3 事件绑定指令 3.3.1 内联语句 3.3…

大数据学习(57)-DataX基础

&&大数据学习&& &#x1f525;系列专栏&#xff1a; &#x1f451;哲学语录: 承认自己的无知&#xff0c;乃是开启智慧的大门 &#x1f496;如果觉得博主的文章还不错的话&#xff0c;请点赞&#x1f44d;收藏⭐️留言&#x1f4dd;支持一下博主哦&#x1f91…

【DeepSeek】Ubuntu快速部署DeepSeek(Ollama方式)

文章目录 人人都该学习的DeepSeekDeepSeek不同版本功能差异DeepSeek与硬件直接的关系DeepSeek系统兼容性部署方式选择部署步骤&#xff08;Ollama方式&#xff09;1.选定适合的deepseek版本2.环境准备3.安装Ollama4.部署deepseek5.测试使用 人人都该学习的DeepSeek DeepSeek 作…

Go加spy++隐藏窗口

最近发现有些软件的窗口就像狗皮膏药一样&#xff0c;关也关不掉&#xff0c;一点就要登录&#xff0c;属实是有点不爽了。 窗口的进程不能杀死&#xff0c;但是窗口我不想要。思路很简单&#xff0c;用 spy 找到要隐藏的窗口的句柄&#xff0c;然后调用 Windows 的 ShowWindo…

最新版本TOMCAT+IntelliJ IDEA+MAVEN项目创建(JAVAWEB)

前期所需&#xff1a; 1.apache-tomcat-10.1.18-windows-x64&#xff08;tomcat 10.1.8版本或者差不多新的版本都可以&#xff09; 2.IntelliJ idea 24年版本 或更高版本 3.已经配置好MAVEN了&#xff08;一定先配置MAVEN再搞TOMCAT会事半功倍很多&#xff09; 如果有没配置…

【大学生体质】智能 AI 旅游推荐平台(Vue+SpringBoot3)-完整部署教程

智能 AI 旅游推荐平台开源文档 项目前端地址 ☀️项目介绍 智能 AI 旅游推荐平台&#xff08;Intelligent AI Travel Recommendation Platform&#xff09;是一个利用 AI 模型和数据分析为用户提供个性化旅游路线推荐、景点评分、旅游攻略分享等功能的综合性系统。该系统融合…

linux 系统内核查询

1. 使用uname命令 uname命令可以用来显示系统信息&#xff0c;包括内核版本。 查看完整的内核版本信息&#xff1a;uname -a [rootlocalhost ~]# uname -a Linux localhost.localdomain 4.18.0-448.el8.x86_64 #1 SMP Wed Jan 18 15:02:46 UTC 2023 x86_64 x86_64 x86_64 GN…

静态时序分析:报告命令report_timing详解

相关阅读 静态时序分析https://blog.csdn.net/weixin_45791458/category_12567571.html?spm1001.2014.3001.5482 目录 指定时序路径起点 指定时序路径经过点 指定时序路径终点 指定不报告的路径 指定路径类型 指定延迟类型 指定每个终点报告的最大时序路径数 指定每个时序组的…

DeepSeek 助力 Vue3 开发:打造丝滑的弹性布局(Flexbox)

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 Deep…

qt 播放pcm音频

一、获取PCM音频 ffmpeg -i input.mp3 -acodec pcm_s16le -ar 44100 -ac 2 -f s16le output.pcm -acodec pcm_s16le&#xff1a;指定16位小端PCM编码格式&#xff08;兼容性最佳&#xff09;-ar 44100&#xff1a;设置采样率为CD标准44.1kHz&#xff08;可替换为16000/8000等&a…

OpenCV计算摄影学(15)无缝克隆(Seamless Cloning)调整图像颜色的函数colorChange()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 cv::colorChange 是 OpenCV 中用于调整图像颜色的函数。它允许你通过乘以不同的系数来独立地改变输入图像中红色、绿色和蓝色通道的强度&#xf…

WPS AI+office-ai的安装、使用

** 说明&#xff1a;WPS AI和OfficeAI是两个独立的AI助手&#xff0c;下面分别简单讲下如何使用 ** WPS AI WPS AI是WPS自带AI工具 打开新版WPS&#xff0c;新建文档后就可以看到菜单栏多了一个“WPS AI”菜单&#xff0c;点击该菜单&#xff0c;发现下方出现很多菜单&#xf…

绝美焦糖暖色调复古风景画面Lr调色教程,手机滤镜PS+Lightroom预设下载!

调色教程 通过 Lr 软件丰富的工具和功能&#xff0c;对风景照片在色彩、影调等方面进行调整。例如利用基本参数调整选项&#xff0c;精准控制照片亮度、对比度、色温、色调等基础要素&#xff1b;运用 HSL 面板可对不同色彩的色相、饱和度以及明亮度进行单独调节&#xff1b;利…

【Manus资料合集】激活码内测渠道+《Manus Al:Agent应用的ChatGPT时刻》(附资源)

DeepSeek 之后&#xff0c;又一个AI沸腾&#xff0c;冲击的不仅仅是通用大模型。 ——全球首款通用AI Agent的破圈启示录 2025年3月6日凌晨&#xff0c;全球AI圈被一款名为Manus的产品彻底点燃。由Monica团队&#xff08;隶属中国夜莺科技&#xff09;推出的“全球首款通用AI…

团队学习—系统思考

3月的团队学习将继续深入&#xff0c;与海外顾问共同探讨系统思考和心智模式的核心内容。回顾过去&#xff0c;这些每月一次的学习不仅成为我们审视思维框架的常规途径&#xff0c;更成为揭示潜在问题与盲点的高效工具。尤其是那些“我不知道我不知道”的领域&#xff0c;外部顾…

使用LVGL驱动三色墨水屏,Arduino

一、基本情况 本文代码基于以下软件版本&#xff1a; Arduino2.X LVGL8.3.11 GXEPD1.6.2 epdpaint---微雪EPD驱动的一部分&#xff0c;你可以在微雪的官网下载到 硬件&#xff1a; MCU&#xff1a;ESP32-S3-N16R8 屏幕&#xff1a;GDEY042Z98黑白红三色墨水屏&#xff0c;某…

DeepSeek未来发展趋势:开创智能时代的新风口

DeepSeek未来发展趋势&#xff1a;开创智能时代的新风口 随着人工智能&#xff08;AI&#xff09;、深度学习&#xff08;DL&#xff09;和大数据的飞速发展&#xff0c;众多创新型技术已经逐渐走向成熟&#xff0c;而DeepSeek作为这一领域的新兴力量&#xff0c;正逐步吸引越…