【 Threejs 】- Shader 着色器实例渲染教程

news2025/1/27 12:40:37

着色器在threejs中是一个难点,话不多说,先来看看着色器是什么?

如果您已经有使用计算机绘图的经验,您就会知道在这个过程中您先画一个圆,然后画一个矩形、一条线、一些三角形,直到您组成您想要的图像。这个过程与手写一封信或一本书非常相似——它是一组指令,一个接一个地完成任务。> 着色器也是一组指令,但这些指令是针对屏幕上的每个像素一次性执行的。这意味着您编写的代码必须根据像素在屏幕上的位置而有所不同。就像打字机一样,您的程序将作为接收位置并返回颜色的函数工作,并且在编译时运行速度非常快。

着色器的来源

那么好,说人话,着色器到底是什么?字面意思,给物品上色的(简单_

关于着色器(shader) 它是用GLSL(着色器语言)写的,执行shader的是GPU,我们可以把一部分GLSL的工作交给GPU来提升性能。这样就好理解了,着色器程序通常在计算机的图形处理单元 (GPU) 上运行,它们可以在其中并行运行。

GLSL官方解释为 :OpenGL Shading Language 也称作 GLslang,是一个以C语言为基础的高阶着色语言。它是由 OpenGL ARB 所建立,提供开发者对绘图管线更多的直接控制,而无需使用汇编语言或硬件规格语言。

  • 有些人就会问了,为什么使用着色器?
  • 答:因为它快啊!

为了回答这个问题,我介绍了并行处理的奇迹。

将你的计算机的 CPU 想象成一个大型工业管道,每项任务都是通过它的东西 - 就像一条工厂生产线。有些任务比其他任务更大,这意味着它们需要更多的时间和精力来处理。由于计算机的体系结构,作业被迫连续运行;每项工作必须一次完成一项。现代计算机通常有四个处理器组,它们像这些管道一样工作,一个接一个地完成任务以保持运行顺畅。每个管道也称为线程

视频游戏和其他图形应用程序比其他程序需要更多的处理能力。由于它们的图形内容,它们必须进行大量的逐像素操作。屏幕上的每个像素都需要计算,在 3D 游戏中,几何和透视也需要计算。

让我们回到管道和任务的比喻。屏幕上的每个像素代表一个简单的小任务。单独每个像素任务对 CPU 来说不是问题,但是(这就是问题所在)必须对屏幕上的每个像素完成小任务!这意味着在旧的 800x600 屏幕中,每帧必须处理 480,000 个像素,这意味着每秒要进行 14,400,000 次计算!是的!这是一个足以使微处理器过载的问题。在以每秒 60 帧的速度运行的现代 2880x1800 视网膜显示器中,该计算加起来达到每秒 311,040,000 次计算。图形工程师如何解决这个问题?

这是并行处理成为一个很好的解决方案的时候。与其拥有几个大而强大的微处理器或_管道_,不如让许多微型微处理器同时并行运行更聪明。这就是图形处理器单元 (GPU)。

将微型微处理器想象成一张管道表,将每个像素的数据想象成一个乒乓球。每秒 14,400,000 个乒乓球几乎可以阻塞任何管道。但是每秒接收 30 个 480,000 像素波的 800x600 微型管道表可以顺利处理。这在更高的分辨率下同样有效——你拥有的并行硬件越多,它可以管理的流就越大。

GPU 的另一个“超能力”是通过硬件加速的特殊数学函数,因此复杂的数学运算直接由微芯片而不是软件来解决。这意味着超快的三角函数和矩阵运算 - 与电流一样快。

痛并快乐着

下面将从一个实例来介绍着色器的使用,听咱给你细细道来~ 着色器的功能和拓展很丰富,如果你对UI和一些图形学感兴趣,那么你学shader就很容易上手,但对我们一些前端学习者们,依旧要迎难而上。不过首先你得对threejs有一定的了解,当然你可以参考我的上一篇文章里面也有对threejs的分享和一些整合:juejin.cn/post/716652…

案例分析

首先拿到图片,给我了三个信号:会动的 有颜色的 立体盒子,旋转好说,立体盒子更好说,有颜色嘛?好像也不难,那岂不是很easy!?

Action:

  • 在工作区新建一个文件夹 threejs_box 并cd进入该文件夹
  • npm init -y + npm i parcel-bundler + npm i three 初始化 并安装依赖
  • 在该文件夹下新建src文件下 并创建 main.js 和 index.html 文件
  • 最后在生成的package文件中的"scripts"里把原有的替换为
 "dev": "parcel src/index.html","build": "parcel build src/index.html" 

这样一个基础框架就搭好了 接下来index.html里

<body><div id="container"></div><script src="./main.js" type="module"></script>
</body> 

main.js中 创建threejs的三要素摄像机,场景,渲染器,以及加一个clock时钟。

import * as THREE from 'three';
let camera, scene, renderer, clock;
init();
animate();
function init() {// 绑定到DOMconst container = document.getElementById( 'container' );// 创建摄像机camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 3000 );camera.position.z = 4;// 创建场景scene = new THREE.Scene();// 创建时钟clock = new THREE.Clock();// 创建渲染器renderer = new THREE.WebGLRenderer();renderer.setPixelRatio( window.devicePixelRatio );container.appendChild( renderer.domElement );onWindowResize();window.addEventListener( 'resize', onWindowResize );
}
// 自适应屏幕
function onWindowResize() {camera.aspect = window.innerWidth / window.innerHeight;camera.updateProjectionMatrix();renderer.setSize( window.innerWidth, window.innerHeight );
}
function animate() {requestAnimationFrame( animate );render();
}
function render() {renderer.render( scene, camera );
} 

接下来要引入着色器的使用

着色器的分类

shaders有两种,一个是vertex shaders(顶点着色器),另一个是fragment shaders(片元着色器)

顶点着色器

这样的script标签,浏览器不能识别,所以不会执行,我们需要通过threejs来执行

  • uniform float time , uniform变量可以在顶点着色器和片元着色器中共同使用,time 变量在运行的时候,是以毫秒为单位。
  • varing vec2 vUv , varing 变量是顶点着色器和片元着色器的接口,这里的vUv保存的是UV映射,存储每个顶点的位置关系,我们可以使用vUv在片元着色器中。
  • void main 函数,所有的着色器必须要有这个函数,在函数中可以把uv映射进行转换(转换顶点位置),最后,我们使用gl_position来实际改变每个顶点的位置,这个改变位置的通用公式是projectionMatrix * modelViewMatrix * vec4(newPosition , 1.0),使用乘积,如果没有这一步,GPU将不会识别我们对UV进行的操作(不会修改UV的位置)

片元着色器

前两个变量是不变的,需要记住的是,所有要用到的变量,都要写在每个着色器中(片元和顶点)

  • 在main函数中,我们通过uv和time 来计算颜色(颜色必须是正的)
  • gl_FragColor 来设置片元的颜色。
  • 这里只是设置好了顶点着色器和片元着色器,还需要在threejs中结合shaderMaterial材质进行设计

THree.shaderMaterial

先定义一个uniforms变量(只是个对象结构的变量)

定义shaderMaterial材质,使用了uniforms变量,还有两个定义的着色器!

  • 为什么要定义这个unifroms变量? 用于修改uv(顶点的位置),这里定义了time,修改time也是同样的效果。

使用总结

1.顶点着色器和片元着色器需要保持变量统一,uniform变量和varying变量
2.顶点着色器和片元着色器的都需要main函数
3.顶点着色器的实际修改是gl_Position,片元着色器的实际修改是gl_FragColor
4.浏览器不执行着色器语言,需要借助Threejs
5.Threejs使用ShaderMaterial来使用着色器,可以通过变量间接修改着色器内容!
6.着色器是通过GPU渲染的,不会占用CPU资源

好了接下来所学即所用,把这些枯燥的知识运用到代码当中。

  • main.js 完整代码
import * as THREE from 'three';

let camera, scene, renderer, clock;
let uniforms1,uniforms2;
init();
animate();function init() {const container = document.getElementById( 'container' );camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 3000 );camera.position.z = 4;scene = new THREE.Scene();clock = new THREE.Clock();// 构建基本的立体框大小const geometry = new THREE.BoxGeometry( 0.75, 0.75, 0.75 );// 创建第一个变量uniforms1 = {'time': { value: 1.0 }};// 创建第二个变量uniforms2 = {'time': { value: 1.0 },// 第二个变量与第一个不同的就在这里,添加了图片贴图'colorTexture': { value: new THREE.TextureLoader().load( '../texture/disturb.jpeg' ) }};// 把他的样式重复并且对称uniforms2[ 'colorTexture' ].value.wrapS = uniforms2[ 'colorTexture' ].value.wrapT = THREE.RepeatWrapping;// 创建数组来保存这四个立体框const params = [[ 'fragment_shader1', uniforms1 ],[ 'fragment_shader2', uniforms2 ],[ 'fragment_shader3', uniforms1 ],[ 'fragment_shader4', uniforms1 ]];// 循环输出 并渲染到页面for ( let i = 0; i < params.length; i ++ ) {// geometry已经在前面确定了 这里只需要定material就行了const material = new THREE.ShaderMaterial( {// 定义的uniforms 用于修改顶点位置uniforms: params[ i ][ 1 ],// 顶点着色器 绑定vertexShader: document.getElementById( 'vertexShader' ).textContent,// 片元着色器绑定fragmentShader: document.getElementById( params[ i ][ 0 ] ).textContent} );// 绑定geometry 和 materialconst mesh = new THREE.Mesh( geometry, material );// 设置位置mesh.position.x = i - ( params.length - 1 ) / 2;mesh.position.y = i % 2 - 0.5;// 添加到场景scene.add( mesh );}renderer = new THREE.WebGLRenderer();renderer.setPixelRatio( window.devicePixelRatio );container.appendChild( renderer.domElement );onWindowResize();window.addEventListener( 'resize', onWindowResize );}function onWindowResize() {camera.aspect = window.innerWidth / window.innerHeight;camera.updateProjectionMatrix();renderer.setSize( window.innerWidth, window.innerHeight );}function animate() {requestAnimationFrame( animate );render();}function render() {// 设置旋转周期时间 - 获得前后两次执行该方法的时间间隔const delta = clock.getDelta();uniforms1[ 'time' ].value += delta * 5;uniforms2[ 'time' ].value = clock.elapsedTime;for ( let i = 0; i < scene.children.length; i ++ ) {const object = scene.children[ i ];object.rotation.y += delta * 0.5 * ( i % 2 ? 1 : - 1 );object.rotation.x += delta * 0.5 * ( i % 2 ? - 1 : 1 );}// 将场景和摄像机渲染到页面renderer.render( scene, camera );} 
  • index.html
<!DOCTYPE html>
<html lang="en">
	<head>
		<title>three.js webgl - shader [Monjori]</title>
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
	</head>
	<body>

		<div id="container"></div>
		<script id="fragment_shader4" type="x-shader/x-fragment">uniform float time;varying vec2 vUv;void main( void ) {
			vec2 position = - 1.0 + 2.0 * vUv;
			float red = abs( sin( position.x * position.y + time / 5.0 ) );
			float green = abs( sin( position.x * position.y + time / 4.0 ) );
			float blue = abs( sin( position.x * position.y + time / 3.0 ) );
			gl_FragColor = vec4( red, green, blue, 1.0 );}
		</script>

		<script id="fragment_shader3" type="x-shader/x-fragment">
			uniform float time;
			varying vec2 vUv;
			void main( void ) {vec2 position = vUv;float color = 0.0;color += sin( position.x * cos( time / 15.0 ) * 80.0 ) + cos( position.y * cos( time / 15.0 ) * 10.0 );color += sin( position.y * sin( time / 10.0 ) * 40.0 ) + cos( position.x * sin( time / 25.0 ) * 40.0 );color += sin( position.x * sin( time / 5.0 ) * 10.0 ) + sin( position.y * sin( time / 35.0 ) * 80.0 );color *= sin( time / 10.0 ) * 0.5;gl_FragColor = vec4( vec3( color, color * 0.5, sin( color + time / 3.0 ) * 0.75 ), 1.0 );
			}

		</script>

		<script id="fragment_shader2" type="x-shader/x-fragment">
			uniform float time;
			uniform sampler2D colorTexture;
			varying vec2 vUv;
			void main( void ) {vec2 position = - 1.0 + 2.0 * vUv;float a = atan( position.y, position.x );float r = sqrt( dot( position, position ) );vec2 uv;uv.x = cos( a ) / r;uv.y = sin( a ) / r;uv /= 10.0;uv += time * 0.05;vec3 color = texture2D( colorTexture, uv ).rgb;gl_FragColor = vec4( color * r * 1.5, 1.0 );
			}
		</script>

		<script id="fragment_shader1" type="x-shader/x-fragment">
			uniform float time;
			varying vec2 vUv;
			void main(void) {vec2 p = - 1.0 + 2.0 * vUv;float a = time * 40.0;float d, e, f, g = 1.0 / 40.0 ,h ,i ,r ,q;e = 400.0 * ( p.x * 0.5 + 0.5 );f = 400.0 * ( p.y * 0.5 + 0.5 );i = 200.0 + sin( e * g + a / 150.0 ) * 20.0;d = 200.0 + cos( f * g / 2.0 ) * 18.0 + cos( e * g ) * 7.0;r = sqrt( pow( abs( i - e ), 2.0 ) + pow( abs( d - f ), 2.0 ) );q = f / r;e = ( r * cos( q ) ) - a / 2.0;f = ( r * sin( q ) ) - a / 2.0;d = sin( e * g ) * 176.0 + sin( e * g ) * 164.0 + r;h = ( ( f + d ) + a / 2.0 ) * g;i = cos( h + r * p.x / 1.3 ) * ( e + e + a ) + cos( q * g * 6.0 ) * ( r + h / 3.0 );h = sin( f * g ) * 144.0 - sin( e * g ) * 212.0 * p.x;h = ( h + ( f - e ) * q + sin( r - ( a + h ) / 7.0 ) * 10.0 + i / 4.0 ) * g;i += cos( h * 2.3 * sin( a / 350.0 - q ) ) * 184.0 * sin( q - ( r * 4.3 + a / 12.0 ) * g ) + tan( r * g + h ) * 184.0 * cos( r * g + h );i = mod( i / 5.6, 256.0 ) / 64.0;if ( i < 0.0 ) i += 4.0;if ( i >= 2.0 ) i = 4.0 - i;d = r / 350.0;d += sin( d * d * 8.0 ) * 0.52;f = ( sin( a * g ) + 1.0 ) / 2.0;gl_FragColor = vec4( vec3( f * i / 1.6, i / 2.0 + d / 13.0, i ) * d * p.x + vec3( i / 1.3 + d / 8.0, i / 2.0 + d / 18.0, i ) * d * ( 1.0 - p.x ), 1.0 );
			}

		</script>

		<script id="vertexShader" type="x-shader/x-vertex">
			varying vec2 vUv;
			void main(){vUv = uv;vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );gl_Position = projectionMatrix * mvPosition;}
		</script>

		<script src="./main.js" type="module"></script>

	</body>
</html> 

最后

最近找到一个VUE的文档,它将VUE的各个知识点进行了总结,整理成了《Vue 开发必须知道的36个技巧》。内容比较详实,对各个知识点的讲解也十分到位。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

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

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

相关文章

面试真题 | 什么是 Redis ? Redis缓存应用场景有哪些?

面试官问题 redis击穿、穿透有什么区别&#xff1f;如何设计用例及测试 Redis 的基本概念 在没有添加 Redis 的时候&#xff0c;后端的查询流程是&#xff1a; 用户访问页面。请求后端服务。经过逻辑处理后&#xff0c;去数据库查询信息。 在添加 Redis 的之后&#xff0c;…

MySQL 服务端口大全

介绍 MySQL默认服务端口3306/TCP都不会陌生&#xff0c;但MySQL提供服务只有单纯的这个端口吗。在8.0版本默认启动的时候会发现&#xff0c;出现新的端口。 可以说MySQL使用的端口数量取决于所启用的特性、所使用的组件、应用程序连接的方式以及环境的其他方面。 按照官方说…

转速传感器信号隔离变送器正弦波输入方波信号输出

特点 转速传感器信号直接输入&#xff0c;方波信号输出正弦波、锯齿波信号输入&#xff0c;方波信号输出200mV峰值微弱信号的放大与整形不改变原波形频率&#xff0c;响应速度快电源、信号&#xff1a;输入/输出 3000VDC三隔离辅助电源&#xff1a;5V、12V、15V或24V直流单电源…

Huffman编码

目录背景Huffman编码代码部分背景 在数据传输&#xff0c;保存的时候&#xff0c;特别是在数据量特别大的时候传输&#xff0c;保存数据是一件特别麻烦的事。比如逛淘宝的时候&#xff0c;首页会有很多商家展示自己产品的高清图片&#xff0c;如果不对图片进行压缩服务端保存图…

经历百度、美团两次被裁后,我能在小公司躺平吗?

百度裁员后我进入体制内&#xff0c;专心学习自动化 百度被裁后&#xff0c;我意识到自学效果不佳&#xff0c;跟不上职场的所需&#xff0c;于是有了系统学习的想法。 这时的新工作是在体制内&#xff0c;工作强度不大&#xff0c;时间上也比较自由&#xff0c;便正式成为了…

非零基础自学Golang 第12章 接口与类型 12.5 类型断言

非零基础自学Golang 文章目录非零基础自学Golang第12章 接口与类型12.5 类型断言12.5.1 ok-pattern12.5.2 switch-type第12章 接口与类型 12.5 类型断言 类型断言是使用在接口变量上的操作。 简单来说&#xff0c;接口类型向普通类型的转换就是类型断言。 类型断言的语法是…

【关于时间序列的ML】项目 1 :使用 Python 进行 Covid-19 病例 预测

&#x1f50e;大家好&#xff0c;我是Sonhhxg_柒&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流&#x1f50e; &#x1f4dd;个人主页&#xff0d;Sonhhxg_柒的博客_CSDN博客 &#x1f4c3; &#x1f381;欢迎各位→点赞…

rk3568 | 瑞芯微平台GPIO引脚驱动编写

最近在玩瑞芯微平台的产品&#xff0c;移植了几个设备的驱动&#xff0c;遇到了一些问题&#xff0c;总结后发现大部分问题都出在了GPIO配置的问题上&#xff0c;写下本篇文章&#xff0c;用来分享一下调试的心得。 有喜欢瑞芯微的朋友&#xff0c;可以加我好友&#xff0c;拉…

JVM的作用,结构

源文件经过编译&#xff0c;生成字节码文件 JVM执行字节码文件&#xff08;实际上就是将字节码解释成具体平台上的机器指令&#xff09; jdk&#xff0c;jre&#xff0c;jvm三者的关系&#xff1a; jvm的组成&#xff1a; (1)类加载器子系统:负责将.class文件加载到JVM中 (2)…

初学编程,我们应该怎么做,十年老鸟带你入门。

问问自己学编程的真正目的&#xff0c;仅仅是想应付考试考证&#xff0c;还是真心想从事编程方面的工作。仅仅处于功利性而不是真心喜欢&#xff0c;人生苦短&#xff0c;劝不要来浪费时间&#xff0c;找其它真心喜欢的事情。不是社会喜欢的&#xff0c;不是父母喜欢的&#xf…

这6个微信隐藏功能你真的知道吗?学到就是赚到

我们常用的软件——微信&#xff0c;它有着许多隐藏功能&#xff0c;这些功能其实是很好用的&#xff0c;但是一直被我们忽略掉&#xff0c;现在我整理出来了&#xff0c;一起来看看吧。1.登录设备管理 我们平常在其它设备登录&#xff0c;第一次登录需要二次验证才能成功&…

jdk11新特性——新的Epsilon垃圾收集器

目录一、Epsilon垃圾收集器概述二、Epsilon垃圾收集器用法三、Epsilon垃圾收集器代码示例四、使用Epsilon垃圾收集器的原因五、使用Epsilon垃圾收集器的主要用途一、Epsilon垃圾收集器概述 A NoOp Garbage CollectorJDK上对这个特性的描述是: 开发一个处理内存分配但不实现任何…

【Flink】Flink GET operation failed: Server side error 从blobserver下载错误

文章目录 1.概述2.服务器端1.概述 flink 报错 Flink GET operation failed: Server side error 从blobserver下载错误 java.io.IOException: GET operation failed: Server side error: /tmp/hadoop-www/nm-lo

技术栈入门-------Redis

使用redis的准备工作 1、在虚拟机上安装redis&#xff08;前提是安装了docker容器&#xff09; 上面使用到的命令 docker pull redis mkdir -p /mydata/redis/confdocker run -p 6379:6379 --name redis -v /mydata/redis/data:/data \ -v /mydata/redis/conf/redis.conf:/et…

Python相关软件下载教程

前言 想要在电脑端运行python程序&#xff0c;需要先下载三个软件&#xff1a;Python解释器&#xff0c;编辑器&#xff08;使用Visual Studio Code&#xff0c;简称VS Code&#xff09;&#xff0c;python软件包管理系统&#xff08;简称pip&#xff09;。 一、MacOS系统安装…

如何使用ArcGIS进行点抽稀(优化版)

概述 有的时候我们手上的数据很密集&#xff08;比如POI数据&#xff09;&#xff0c;全部加载出来会很挤&#xff0c;在我们只需要部分数据的情况下就需要对其进行抽稀&#xff0c;这里为大家介绍一种比较简单的抽稀方法&#xff0c;希望能对大家有所帮助。 按百分比抽稀 在…

ICG-Hydrazide,用于光热治疗或光动力治疗

ICG能够强烈地吸收光能将其转化为热能或产生单线态氧&#xff0c;可用于光热治疗(PTT)或光动力治疗(PDT)。 英文名称&#xff1a;ICG-Hydrazide 外观&#xff1a;固体/粉末 质量纯度&#xff1a;95% 储存条件&#xff1a;-20℃ 结构式&#xff1a; 凯新生物运输说明: 极低…

Linux学习-87-LNMP一键安装过程

17.16 LNMP安装的前期准备&#xff08;LNMP一键安装包下载&#xff09; 手工安装 LNMP 环境&#xff0c;那么同样需要安装大概 10多个源码包&#xff08;根据版本和功能不同而不同&#xff09;。不过&#xff0c;现在网上非常流行的 LNMP 环境的搭建过程是采用 LNMP 一键安装包…

25岁,放弃4年所学专业,年薪20W+,我选择了转行。

25岁,没被迫转行, 是主动选择转行&#xff0c;放弃海外20W年薪“稳定”生活&#xff0c;目前已辞职&#xff0c;正在休整准备寻找工作中。希望我的经历可以给大家带来一点启发和借鉴。 首先自我介绍下。90后大叔&#xff0c;土木工程专业&#xff0c;2017年毕业于中南搬砖摇篮…

非零基础自学Golang 第12章 接口与类型 12.6 小结 12.7 知识拓展

非零基础自学Golang 文章目录非零基础自学Golang第12章 接口与类型12.6 小结12.7 知识拓展12.7.1 非侵入式接口第12章 接口与类型 12.6 小结 了解什么是鸭子类型。掌握接口的定义与实现。了解什么是接口嵌入。掌握空接口的常见用法&#xff0c;如空接口的赋值操作。掌握类型断…