记录–Threejs-着色器实现一个水波纹

  • 记录–Threejs-着色器实现一个水波纹已关闭评论
  • 224 次浏览
  • A+
所属分类:Web前端
摘要

hree.js 是一个基于 WebGL 的 JavaScript 3D 库,用于创建和渲染 3D 图形场景。webGL: WebGL 是一种基于 JavaScript API 的图形库,它允许在浏览器中进行高性能的 3D 图形渲染。webGL的渲染依赖于底层GPU的渲染能力。
通过获取<canvas>元素获取WebGL的上下文,从而获得WebGL API和GPU。
GPU 图形处理器:处理图形计算的硬件。GPU运行着一个着色器小程序。包含两种类型的着色器程序,顶点着色器和片元着色器。


这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

记录--Threejs-着色器实现一个水波纹

hree.js 是一个基于 WebGL 的 JavaScript 3D 库,用于创建和渲染 3D 图形场景。

一、 图像渲染过程

1、webGL

webGL: WebGL 是一种基于 JavaScript API 的图形库,它允许在浏览器中进行高性能的 3D 图形渲染。webGL的渲染依赖于底层GPU的渲染能力。
通过获取<canvas>元素获取WebGL的上下文,从而获得WebGL API和GPU。
GPU 图形处理器:处理图形计算的硬件。GPU运行着一个着色器小程序。包含两种类型的着色器程序,顶点着色器片元着色器

2、着色器

着色器:

记录--Threejs-着色器实现一个水波纹

3、坐标系

(1)模型空间:物体在其自身坐标系下的位置、大小、方向。
(2)世界空间:所有模型都放置在同一坐标系下的空间。每个物体都有唯一的坐标系。如three.js的AxesHelper是基于世界空间创建的坐标系。
(3)视图空间:相机所在的坐标系。简单来说就是以相机为原点,物体在相机眼中的位置。
(4)投影空间:将3D图形投影到二维屏幕上的坐标系。将3D坐标转化为2D坐标。
各个坐标系之间的转换:通过矩阵变换来完成。例如,将物体从模型空间转换到世界空间,可以使用模型变换矩阵将局部坐标转换为全局坐标。将物体从世界空间转换到视图空间,可以使用相机变换矩阵将全局坐标变换为相机坐标。最后,将视图空间中的坐标投影到屏幕上,可以使用投影变换矩阵将相机坐标变换为裁剪坐标。通过这些矩阵变换,可以将坐标从一个空间转换到另一个空间,从而实现3D图形的渲染和显示。

4、GPU渲染过程

(1)渲染管线:就是将3D坐标转化为屏幕像素(屏幕都是二维的,也就是二维坐标)的过程。分为以下几个阶段。
应用阶段:由CPU控制,主要负责数据的准备和处理。CPU将数据发送的GPU,包括图形的顶点坐标、纹理坐标、颜色信息等 。
几何阶段:运行在GPU中。将顶点坐标变换到屏幕空间中。
光栅化阶段:阶段运行在GPU中。光栅化阶段主要将渲染图元转换为像素,并进行颜色插值、纹理采样等处理,最终输出渲染像素。

(2)GPU具体渲染过程。

记录--Threejs-着色器实现一个水波纹

 齐次裁剪空间:简单来说就是相机视锥体的范围。如下图

记录--Threejs-着色器实现一个水波纹

二、着色器材质

three.js中有两个着色器材质ShaderMaterial和原始着色器材质RawShaderMaterial,它是用着色器语言GLSL编写的程序,可以让我们自定义物体的着色器程序,从而实现复杂的效果。 1、ShaderMaterial:材质接收两个着色器,顶点着色器和片元着色器。着色器代码需要我们自己编写,来实现复杂的效果。来看下如何使用。

用着色器材质实现下面这个效果:

记录--Threejs-着色器实现一个水波纹

搭建目录结构和基础看这里

1、首先搭建一个three.js场景

<!DOCTYPE html> <html lang="en"> <head>     <meta charset="UTF-8">     <title>threejs_collision</title>     <link rel="stylesheet" href="./asstes/css/style.css"> </head> <body>     <script src="./main/index.js" type="module"></script> </body> </html> 

*{     padding: 0;     margin: 0; } body,html {     background: green; }

import * as THREE from "three"; import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"; // 创建场景 const scene = new THREE.Scene(); // 创建相机 const camera = new THREE.PerspectiveCamera(     75,     window.innerWidth / window.innerHeight,     0.1,     1000 ); // 设置相机位置 camera.position.set(0, 0, 10); //将相机添加到 scene.add(camera);  //创建环境光,环境光会均匀的照亮场景中的所有物体。 const light = new THREE.AmbientLight(0x404040); //将环境光添加到场景 scene.add(light); // 创建平行光 const directionalLight = new THREE.DirectionalLight(); //设置光源位置 directionalLight.position.set(0, 5, 0); //添加到场景 scene.add(directionalLight); //设置光源投射阴影 directionalLight.castShadow= true  // 创建渲染器 const renderer = new THREE.WebGLRenderer(); // 设置渲染器尺寸 renderer.setSize(window.innerWidth, window.innerHeight); //开启渲染器阴影计算 renderer.shadowMap.enabled = true //将canvas添加到body中 document.body.appendChild(renderer.domElement);  // 轨道控制器 const controls = new OrbitControls(camera, renderer.domElement); // 轨道控制器的阻尼感 controls.enableDamping = true; //辅助坐标轴 const axesHelp = new THREE.AxesHelper(); scene.add(axesHelp);   const clock = new THREE.Clock() //渲染函数 function render() {     //阻尼     controls.update()     let time = clock.getDelta();     renderer.render(scene, camera);     requestAnimationFrame(render); } // 初始化渲染函数 render(); // 监听浏览器窗口尺寸变化 window.addEventListener('resize',() => {     //重新设置相机宽高比     camera.aspect = window.innerWidth / window.innerHeight;     //更新相机投影矩阵     camera.updateProjectionMatrix();     //重新设置渲染器尺寸     renderer.setSize(window.innerWidth,window.innerHeight);     //设置设备像素比     renderer.setPixelRatio(window.devicePixelRatio) })

2、创建着色器材质

从上面动图可以看出,是一个平面贴了一张图,然后给这个平面加了wave的效果。
所以,先创建一个平面。

const planeGeometry = new THREE.PlaneGeometry(1,1,64,64);

引入贴图

const textureLoader = new THREE.TextureLoader(); const texture = textureLoader.load(     require('../asstes/img/texture/xx.jpeg') )

创建一个shader文件夹存放着色器代码,新建一个两个.glsl文件,来写顶点着色器和片元着色器代码。如下:

记录--Threejs-着色器实现一个水波纹
在js文件中引入这两个着色器:

import vertexShaderText from '../shader/basic/vertex.glsl' import fragmentShaderText from '../shader/basic/fragment.glsl'

创建着色器材质

const material = new THREE.ShaderMaterial({     // 顶点着色器     vertexShader:vertexShaderText,     // 片元着色器     fragmentShader:fragmentShaderText,     // 设置两面可见,默认只能看见一面     side:THREE.DoubleSide, })

3、编写着色器代码

顶点着色器 使用的是着色器语言(GLSL),不会也没事,知道怎么接收参数,在哪写逻辑就够了。首先要有一个入口点,也就是下面的void main函数,对数据的处理就写在这里面。
shader中有三种类型的变量: uniforms, attributes, 和 varyings
uniforms:从应用程序(CPU)传到着色器的变量(GPU),顶点着色器和片元着色器都能访问,比如我们可以在ShaderMaterial传递uniforms,在着色器程序中接收。用法:接收:uniform float uTime;
attributes:与每个顶点关联的变量。例如,顶点位置,法线和顶点颜色都是存储在attributes中的数据。attributes  可以在顶点着色器中访问。用法:attribute vec3 position
Varyings:在顶点着色器和片元着色器中传递数据。可以将顶点着色器处理过的数据通过varyings传给片元着色器。用法:varying vec2 vUv;

// 高精度浮点数 precision highp float; void main(){     // vec4 四维向量     vec4 modelPosition = modelMatrix * vec4(position, 1.0);     // projectionMatrix 投影矩阵;viewMatrix 视图矩阵;modelMatrix 模型矩阵;跟上面提到的坐标系对应。这些都是内置的uniform,使用ShaderMaterial会自动到GLSL shader代码中。使用RawShaderMaterial不会自动添加,需要手动接收。     //gl_Position是一个内置变量,它表示经过投影、视图和模型变换后的顶点位置。     gl_Position = projectionMatrix * viewMatrix * modelPosition; }

片元着色器:

// 中等精度浮点数 precision mediump float; void main(){     // gl_FragColor内置对象,片元的颜色值 vec4是个思维变量这里代表了红色分量、绿色分量、蓝色分量和透明度分量。     gl_FragColor = vec4(1.0,1.0,0.0,1.0);     }

效果:注意这个平面的颜色是片元着色器里gl_FragColor对象决定的,现在是写死的(当然也可以写活)。

记录--Threejs-着色器实现一个水波纹

接下来给这个平面添加wave的效果:这个平面在X、y轴,通过改变Z轴的坐标来使平面有上下波动的效果,这个波动的效果像不像正弦余弦曲线,可以通过sin,cos实现这个效果。可以通过Uniforms变量将数据传给顶点着色器和片元着色器。

const clock = new THREE.Clock() //渲染函数 function render() {     let time = clock.getElapsedTime()     material.uniforms.uTime.value = time; }  const material = new THREE.ShaderMaterial({     uniforms:{         uTime:{             value:0         },         // 贴图         uTexture:{             value: texture         }     } })

顶点着色器程序:

precision mediump float; uniform float uTime; //varying:从顶点着色器传递到片元着色器的变量。 将uv传递到片元着色器。uv是二维坐标,是物体顶点在纹理上的映射位置(相当于将一个3维物体展开后的对应的二维位置)。传递给片元着色器可以读取该坐标处的颜色,赋值给gl_FragColor,实现贴图效果。 varying vec2 vUv; void main(){     vUv = uv;     vec4 modelPosition = modelMatrix * vec4(position, 1.0);     modelPosition.z = sin((modelPosition.x + uTime) * 10.0) * 0.05;     modelPosition.z += sin((modelPosition.y + uTime) * 10.0) * 0.05;     gl_Position = projectionMatrix * viewMatrix * modelPosition; }

片元着色器程序:
precision mediump float; // sampler2D类型的纹理变量 uniform sampler2D uTexture; // 接收顶点着色器传来的uv varying vec2 vUv; void main(){      // texture2D是用于读取纹理颜色值的函数     vec4 textureColor = texture2D(uTexture,vUv);     gl_FragColor = textureColor;      }

这样就是实现了以上效果。
记录--Threejs-着色器实现一个水波纹

如果是RawShaderMaterial材质,内置的uniform需要手动去接收,以上代码改成:
顶点着色器程序:

precision mediump float; // 定义顶点 attribute vec3 position; //定义位置参数 attribute vec2 uv; // 传入投影矩阵 uniform mat4 projectionMatrix; // 传入视图矩阵 uniform mat4 viewMatrix; // 传入模型矩阵 uniform mat4 modelMatrix; //接收着色器材质传递的时间参数 uniform float uTime; // uv传递到片元着色器 varying是从顶点着色器传递到片元着色器的变量 varying vec2 vUv; void main(){     vUv = uv;     vec4 modelPosition = modelMatrix * vec4(position,1.0);     modelPosition.z = sin((modelPosition.x + uTime) * 10.0) * 0.05;     modelPosition.z += sin((modelPosition.y + uTime) * 10.0) * 0.05;     gl_Position = projectionMatrix * viewMatrix * modelPosition; }

三、着色器实现一个水波纹

水波纹相对于上面旗帜飘动的效果,多了些随机性。如水波的高度是变化的,波浪的起伏是随机的,高处和低处的颜色不一样,水波波动的大小、频率等。这里用到了一些随机函数。将这些随机性添加给波浪的高度来达到更真实的效果。下面定义了很多参数,这些参数可以自己去调节看看它们是什么作用。

const material = new THREE.ShaderMaterial({     vertexShader:vertexShaderText,     fragmentShader:fragmentShaderText,     side:THREE.DoubleSide,     uniforms:{         uTime:{             value:0         },         uWaresFrequency:{             value:params.uWaresFrequency         },         uScale:{             value:params.uScale         },         uNoiseFrequency:{             value:params.uNoiseFrequency         },         uNoiseScale:{             value: params.uNoiseScale         },         uXzScale:{             value: params.uXzScale         },         uLowColor:{             value:new THREE.Color(params.uLowColor)         },         uHighColor: {             value:new THREE.Color(params.uHighColor)         },         uOpacity:{             value:params.uOpacity         }     },     transparent: true })  const plane = new THREE.Mesh(planeGeometry,material) plane.rotation.x = -Math.PI / 2 scene.add(plane)  // 将这些uniforms变量添加到gui在,方便看效果,找到最合适的值。 gui.add(params,'uWaresFrequency').min(1).max(50).step(0.1).onChange(val => {     material.uniforms.uWaresFrequency.value = val; }); gui.add(params,'uScale').min(0).max(0.2).step(0.01).onChange(val => {     material.uniforms.uScale.value = val; }); gui.add(params,'uNoiseFrequency').min(0).max(100).step(0.1).onChange(val => {     material.uniforms.uNoiseFrequency.value = val; }); gui.add(params,'uNoiseScale').min(0).max(5).step(0.01).onChange(val => {     material.uniforms.uNoiseScale.value = val; }); gui.add(params,'uXzScale').min(1).max(5).step(0.01).onChange(val => {     material.uniforms.uXzScale.value = val; }); gui.addColor(params,'uLowColor').onFinishChange(val => {     material.uniforms.uLowColor.value = new THREE.Color(val) }) gui.addColor(params,'uHighColor').onFinishChange(val => {     material.uniforms.uHighColor.value = new THREE.Color(val) }) gui.add(params,'uOpacity').min(0).max(1).onChange(val => {     material.uniforms.uOpacity.value = val; })

顶点着色器程序:里面的函数都是从这本书里抄的

uniform float uTime; uniform float uWaresFrequency; uniform float uScale; uniform float uNoiseFrequency; uniform float uNoiseScale; uniform float uXzScale; varying float vElevation;  float random (vec2 st) {     return fract(sin(dot(st.xy,vec2(12.9898,78.233)))*43758.5453123); } // 旋转函数 vec2 rotate(vec2 uv, float rotation, vec2 mid) {     return vec2(     cos(rotation) * (uv.x - mid.x) + sin(rotation) * (uv.y - mid.y) + mid.x,     cos(rotation) * (uv.y - mid.y) - sin(rotation) * (uv.x - mid.x) + mid.y     ); }  // 2d噪声函数  float noise (in vec2 st) {     vec2 i = floor(st);     vec2 f = fract(st);     float a = random(i);     float b = random(i + vec2(1.0, 0.0));     float c = random(i + vec2(0.0, 1.0));     float d = random(i + vec2(1.0, 1.0));     vec2 u = f*f*(3.0-2.0*f);     return mix(a, b, u.x) +     (c - a)* u.y * (1.0 - u.x) +     (d - b) * u.x * u.y; } // 随机函数 vec4 permute(vec4 x) {     return mod(((x*34.0)+1.0)*x, 289.0); } vec2 fade(vec2 t) {     return t*t*t*(t*(t*6.0-15.0)+10.0); } float cnoise(vec2 P) {     vec4 Pi = floor(P.xyxy) + vec4(0.0, 0.0, 1.0, 1.0);     vec4 Pf = fract(P.xyxy) - vec4(0.0, 0.0, 1.0, 1.0);     Pi = mod(Pi, 289.0); // To avoid truncation effects in permutation     vec4 ix = Pi.xzxz;     vec4 iy = Pi.yyww;     vec4 fx = Pf.xzxz;     vec4 fy = Pf.yyww;     vec4 i = permute(permute(ix) + iy);     vec4 gx = 2.0 * fract(i * 0.0243902439) - 1.0; // 1/41 = 0.024...     vec4 gy = abs(gx) - 0.5;     vec4 tx = floor(gx + 0.5);     gx = gx - tx;     vec2 g00 = vec2(gx.x,gy.x);     vec2 g10 = vec2(gx.y,gy.y);     vec2 g01 = vec2(gx.z,gy.z);     vec2 g11 = vec2(gx.w,gy.w);     vec4 norm = 1.79284291400159 - 0.85373472095314 * vec4(dot(g00, g00), dot(g01, g01), dot(g10, g10), dot(g11, g11));     g00 *= norm.x;     g01 *= norm.y;     g10 *= norm.z;     g11 *= norm.w;     float n00 = dot(g00, vec2(fx.x, fy.x));     float n10 = dot(g10, vec2(fx.y, fy.y));     float n01 = dot(g01, vec2(fx.z, fy.z));     float n11 = dot(g11, vec2(fx.w, fy.w));     vec2 fade_xy = fade(Pf.xy);     vec2 n_x = mix(vec2(n00, n01), vec2(n10, n11), fade_xy.x);     float n_xy = mix(n_x.x, n_x.y, fade_xy.y);     return 2.3 * n_xy; }  void main() {     vec4 modelPosition = modelMatrix * vec4(position,1.0);     // 波浪高度     float elevation = sin(modelPosition.x * uWaresFrequency) * sin(modelPosition.z * uWaresFrequency * uXzScale);     elevation += cnoise(vec2(modelPosition.xz*uNoiseFrequency+uTime))     *uNoiseScale;     elevation *= uScale;     // 传到片元着色器     vElevation = elevation;     modelPosition.y += elevation;     gl_Position = projectionMatrix * viewMatrix * modelPosition; }

片元着色器程序:
varying float vElevation; uniform vec3 uLowColor; uniform vec3 uHighColor; uniform float uOpacity; void main(){     float a = (vElevation + 1.0) / 2.0;     // 混合颜色     vec3 color = mix(uLowColor,uHighColor,a);     gl_FragColor = vec4(color,uOpacity); }

最终效果(效果可以调节参数,调到自己满意的效果):
记录--Threejs-着色器实现一个水波纹

本文转载于:

https://juejin.cn/post/7248982532728864825

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

 记录--Threejs-着色器实现一个水波纹