
Three js 着色器材质 shader
什么是 GLSL?
GLSL 代表 openGL Shading Language,它是着色器程序的特定标准,您将在接下来的章节中看到。根据硬件和操作系统,还有其他类型的着色器。在这里,我们将使用由Khronos Group监管的 openGL 规范。了解 OpenGL 的历史有助于理解其大部分奇怪的约定,为此我建议您查看:openglbook.com/chapter-0-preface-what-is-opengl.html
着色器材质(ShaderMaterial)
使用自定义shader渲染的材质。 shader是一个用GLSL编写的小程序 ,在GPU上运行。 您可能需要使用自定义shader,如果你要:
——要实现内置 materials 之外的效果。
——将许多对象组合成单个BufferGeometry以提高性能。
ShaderMaterial 只有使用 WebGLRenderer 才可以绘制正常, 因为 vertexShader
和 fragmentShader
属性中GLSL代码必须使用WebGL来编译并运行在GPU中。
顶点着色器和片元着色器
什么是顶点着色器(vertexShader)
它接收attributes, 计算/操纵每个单独顶点的位置,并将其他数据(varyings)传递给片元着色器。
什么是片元着色器(fragmentShader)
它设置渲染到屏幕的每个单独的片元(像素)的颜色。
举例
我们通过自定义的着色器渲染一个平面材质。
变量 | 描述 |
---|---|
gl_Position |
OpenGL中的一个内置变量,用于表示顶点在裁剪空间中的位置。 |
vec4 |
表示一个四维向量,由x、y、z和w四个分量组成 。 |
position |
表示当前的位置 |
gl_FragColor |
是OpenGL中的一个内置变量,用于设置片元的颜色 。它是一个vec4类型的变量,由x、y、z和w四个分量组成 。其中,x、y、z分量表示颜色的红、绿、蓝分量,w分量表示透明度 。 |
这里的gl_FragColor
我设置的是黄色的面板。
// 创建着色器材质
const shaderMaterial = new THREE.ShaderMaterial({
// 顶点着色器
vertexShader: `
void main(){
gl_Position = vec4( position, 1.0 ) ;
}
`,
// 片元着色器
fragmentShader: `
void main(){
gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0);
}
`,
});
// 创建平面
const floor = new THREE.Mesh(
new THREE.PlaneGeometry(1, 1, 64, 64),
shaderMaterial
);
console.log(floor);
// 添加
scene.add(floor);
但这样会发现个问题,由于坐标是一成不变的所以不论你如何移动旋转它将都这样显示,所以的我们在旋转移动鼠标的同时还需要乘以如下几个矩阵:<投影矩阵>*<视图矩阵>*<模型矩阵>*<顶点坐标>
代码改成如下所示,再次运行
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4( position, 1.0 ) ;
相乘的顺序不能改变。物体本身拥有一个坐标系,叫本地坐标系。把物体放到世界坐标系中,采用了模型矩阵,就是执行缩放、平移、旋转操作的过程。此时物体就具有了世界坐标系。
再加入上帝之眼,就是视图矩阵,包括视点坐标,观察点坐标,和上方向。现在只差最后一步–投影矩阵,物体就可以呈现出来了。
目前显示设备都是二维平面的,所以需要投影矩阵来转换物体。投影矩阵通常分为平行投影和透视投影。
名称 | 变量 |
---|---|
投影矩阵 | projectionMatrix |
视图矩阵 | viewMatrix |
模型矩阵 | modelMatrix |
完整代码
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import gsap from "gsap";
import * as dat from "dat.gui";
// 目标:认识shader
//创建gui对象
const gui = new dat.GUI();
// console.log(THREE);
// 初始化场景
const scene = new THREE.Scene();
// 创建透视相机
const camera = new THREE.PerspectiveCamera(
90,
window.innerHeight / window.innerHeight,
0.1,
1000
);
// 设置相机位置
// object3d具有position,属性是1个3维的向量
camera.position.set(0, 0, 2);
// 更新摄像头
camera.aspect = window.innerWidth / window.innerHeight;
// 更新摄像机的投影矩阵
camera.updateProjectionMatrix();
scene.add(camera);
// 加入辅助轴,帮助我们查看3维坐标轴
const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);
// 加载纹理
// 创建纹理加载器对象
const textureLoader = new THREE.TextureLoader();
const texture = textureLoader.load("./texture/ca.jpeg");
const params = {
uFrequency: 10,
uScale: 0.1,
};
// const material = new THREE.MeshBasicMaterial({ color: "#00ff00" });
// 创建着色器材质
const shaderMaterial = new THREE.ShaderMaterial({
// 顶点着色器
vertexShader: `
void main(){
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4( position, 1.0 ) ;
}
`,
// 片元着色器
fragmentShader: `
void main(){
gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0);
}
`,
});
//
// 创建平面
const floor = new THREE.Mesh(
new THREE.PlaneGeometry(1, 1, 64, 64),
shaderMaterial
);
console.log(floor);
scene.add(floor);
// 初始化渲染器
const renderer = new THREE.WebGLRenderer({ alpha: true });
// renderer.shadowMap.enabled = true;
// renderer.shadowMap.type = THREE.BasicShadowMap;
// renderer.shadowMap.type = THREE.VSMShadowMap;
// 设置渲染尺寸大小
renderer.setSize(window.innerWidth, window.innerHeight);
// 监听屏幕大小改变的变化,设置渲染的尺寸
window.addEventListener("resize", () => {
// console.log("resize");
// 更新摄像头
camera.aspect = window.innerWidth / window.innerHeight;
// 更新摄像机的投影矩阵
camera.updateProjectionMatrix();
// 更新渲染器
renderer.setSize(window.innerWidth, window.innerHeight);
// 设置渲染器的像素比例
renderer.setPixelRatio(window.devicePixelRatio);
});
// 将渲染器添加到body
document.body.appendChild(renderer.domElement);
// 初始化控制器
const controls = new OrbitControls(camera, renderer.domElement);
// 设置控制器阻尼
controls.enableDamping = true;
// 设置自动旋转
// controls.autoRotate = true;
const clock = new THREE.Clock();
function animate(t) {
const elapsedTime = clock.getElapsedTime();
// console.log(elapsedTime);
requestAnimationFrame(animate);
// 使用渲染器渲染相机看这个场景的内容渲染出来
renderer.render(scene, camera);
}
animate();
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link rel="stylesheet" href="./assets/css/style.css" />
</head>
<body>
<script src="./main/main.js" type="module"></script>
</body>
</html>
style.css
* {
margin: 0;
padding: 0;
}
body {
background-color: #1e1a20;
}
::-webkit-scrollbar {
display: none;
}
.page {
display: flex;
justify-content: center;
height: 100vh;
padding: 0 10%;
color: #fff;
flex-direction: column;
}
.page h1 {
margin: 60px 0;
font-size: 40px;
}
.page h3 {
font-size: 30px;
}
着色器插件安装与导入
由于OpenGL是一个单独的语言,所以我们更希望它能够单独弄成一个.glsl
文件,在需要的时候进行导入。
首先我们创建shader/basic
文件夹,并创建相关的文件分别存放顶点着色器和片元着色器的代码。
fragment.glsl
void main(){
gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0);
}
vertex.glsl
void main(){
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4( position, 1.0 ) ;
}
在main.js中我们对其进行导入,修改成如下代码。
// 顶点着色器
import basicVertexShader from "../shader/basic/vertex.glsl";
// 片元着色器
import basicFragmentShader from "../shader/basic/fragment.glsl";
...
const shaderMaterial = new THREE.ShaderMaterial({
// 顶点着色器
vertexShader: basicVertexShader,
// 片元着色器
fragmentShader: basicFragmentShader,
});
效果与前面运行一样。
如果想对gl的代码有所高亮显示,可以安装该插件。
原始着色器材质(RawShaderMaterial)
此类的工作方式与ShaderMaterial类似,不同之处在于内置的uniforms和attributes的定义不会自动添加到GLSL shader代码中。
(修改成如下代码)
// 顶点着色器
import basicVertexShader from "../shader/raw/vertex.glsl";
// 片元着色器
import basicFragmentShader from "../shader/raw/fragment.glsl";
...
const rawshaderMaterial = new THREE.RawShaderMaterial({
// 顶点着色器
vertexShader: basicVertexShader,
// 片元着色器
fragmentShader: basicFragmentShader,
});
// 创建平面
const floor = new THREE.Mesh(
new THREE.PlaneGeometry(1, 1, 64, 64),
rawshaderMaterial
);
注意这里我更改了glsl的路径,并在相应的路径下创建了文件,需要注意的是需要自行定义变量。
// fragment.glsl
void main(){
gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0);
}
attribute vec3 position;
uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;
void main(){
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4( position, 1.0 ) ;
}
着色器材质的变量
每个着色器材质都可以指定两种不同类型的shaders,他们是顶点着色器和片元着色器(Vertex shaders and fragment shaders)。
● 顶点着色器首先运行; 它接收attributes, 计算/操纵每个单独顶点的位置,并将其他数据(varyings)传递给片元着色器。
● 片元(或像素)着色器后运行; 它设置渲染到屏幕的每个单独的“片元”(像素)的颜色。
shader中有三种类型的变量: uniforms, attributes, 和 varyings
● Uniforms是所有顶点都具有相同的值的变量。 比如灯光,雾,和阴影贴图就是被储存在uniforms中的数据。 uniforms可以通过顶点着色器和片元着色器来访问。
● Attributes 与每个顶点关联的变量。例如,顶点位置,法线和顶点颜色都是存储在attributes中的数据。attributes 只 可以在顶点着色器中访问。
● Varyings 是从顶点着色器传递到片元着色器的变量。对于每一个片元,每一个varying的值将是相邻顶点值的平滑插值。
着色器传值
如何从顶点着色器的值传入到片元着色器中?
举例我们可以通过定义uv变量来进行传值。
// vertex.glsl
// 设置绘制的高低精度
precision lowp float;
attribute vec3 position;
attribute vec2 uv;
uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;
varying vec2 vUv;
// 设置精度
// highp -2^16 ~ 2^16
// mediump -2^10 ~ 2^10
// lowp -2^8 ~ 2^8
void main(){
vUv = uv;
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4( position, 1.0 ) ;
}
// fragment.glsl
// 设置绘制的高低精度
precision lowp float;
varying vec2 vUv;
void main(){
gl_FragColor = vec4(vUv, 0.0, 1.0);
}
设置线框
通过设置原始着色器的wireframe
属性为true可以显示图像的像框。
// 创建着色器材质
const rawshaderMaterial = new THREE.RawShaderMaterial({
// 顶点着色器
vertexShader: basicVertexShader,
// 片元着色器
fragmentShader: basicFragmentShader,
// 设置线框模式
wireframe: true,
});
// 创建平面
const floor = new THREE.Mesh(
new THREE.PlaneGeometry(1, 1),
rawshaderMaterial
);
设置图像绘制的坐标
设置Position位置x为1,z为1的情况下如何进行显示。
修改vertex.glsl
// 设置绘制的高低精度
precision lowp float;
attribute vec3 position;
attribute vec2 uv;
uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;
varying vec2 vUv;
void main(){
vUv = uv;
vec4 modelPosition = modelMatrix * vec4( position, 1.0 );
modelPosition.x += 1.0;
modelPosition.z += 1.0;
gl_Position = projectionMatrix * viewMatrix * modelPosition ;
}
设置旋转
void main(){
vUv = uv;
vec4 modelPosition = modelMatrix * vec4( position, 1.0 );
// modelPosition.x += 1.0;
// modelPosition.z += 1.0;
modelPosition.z += modelPosition.x;
gl_Position = projectionMatrix * viewMatrix * modelPosition ;
}
或者我们可以使用一些数学函数,例如:sin()
// 或数学函数
modelPosition.z += sin(modelPosition.x* 10.0);
绘制水平弯曲波浪形状
void main(){
vUv = uv;
vec4 modelPosition = modelMatrix * vec4( position, 1.0 );
// 或数学函数
modelPosition.z += sin(modelPosition.x * 100.0)*0.1;
gl_Position = projectionMatrix * viewMatrix * modelPosition ;
}
const rawshaderMaterial = new THREE.RawShaderMaterial({
// 顶点着色器
vertexShader: basicVertexShader,
// 片元着色器
fragmentShader: basicFragmentShader,
// 设置线框模式
// wireframe: true,
// 传递纹理
side: THREE.DoubleSide,
});
绘制y轴的玻璃形状
void main(){
vUv = uv;
vec4 modelPosition = modelMatrix * vec4( position, 1.0 );
// 或数学函数
modelPosition.z += sin(modelPosition.x * 10.0)*0.05;
// 加上y轴的波动
modelPosition.z += sin(modelPosition.y * 10.0)*0.05;
gl_Position = projectionMatrix * viewMatrix * modelPosition ;
}
通过传入z的位置来设置颜色。
// 设置绘制的高低精度
precision lowp float;
attribute vec3 position;
attribute vec2 uv;
uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;
varying vec2 vUv;
varying float vElevation;
// 设置精度
// highp -2^16 ~ 2^16
// mediump -2^10 ~ 2^10
// lowp -2^8 ~ 2^8
void main(){
vUv = uv;
vec4 modelPosition = modelMatrix * vec4( position, 1.0 );
// 或数学函数
modelPosition.z += sin(modelPosition.x * 10.0)*0.05;
// 加上y轴的波动
modelPosition.z += sin(modelPosition.y * 10.0)*0.05;
vElevation = modelPosition.z;
gl_Position = projectionMatrix * viewMatrix * modelPosition ;
}
// 设置绘制的高低精度
precision lowp float;
varying vec2 vUv;
varying float vElevation;
void main(){
// gl_FragColor = vec4(vUv, 0.0, 1.0);
float letvElevation = vElevation + 0.05 * 10.0;
gl_FragColor = vec4(1.0*letvElevation,0.0, 0.0, 1.0);
}
让图像动起来!
通过在传入时间变量,并更具时间变量的不同的移动它的z轴,让其产生波动的效果。
const rawshaderMaterial = new THREE.RawShaderMaterial({
// 顶点着色器
vertexShader: basicVertexShader,
// 片元着色器
fragmentShader: basicFragmentShader,
// 设置线框模式
// wireframe: true,
// 传递纹理
side: THREE.DoubleSide,
// 传递参数
uniforms: {
uTime: {
value: 0
},
},
});
...
function animate(t) {
const elapsedTime = clock.getElapsedTime();
// 传入时间
rawshaderMaterial.uniforms.uTime.value = elapsedTime;
requestAnimationFrame(animate);
// 使用渲染器渲染相机看这个场景的内容渲染出来
renderer.render(scene, camera);
}
// 获取时间
uniform float uTime;
...
// 或数学函数
modelPosition.z += sin((modelPosition.x + uTime) * 10.0)*0.05;
// 加上y轴的波动
modelPosition.z += sin((modelPosition.y + uTime) * 10.0)*0.05;
我们再添加2022残奥会的图片,在fragment.glsl
文件中进行修改并添加纹理。
import image from '../assets/ca.jpeg'
...
const texture = textureLoader.load(image);
...
const rawshaderMaterial = new THREE.RawShaderMaterial({
...
// 传递参数
uniforms: {
uTime: {
value: 0
},
uTexture: {
value: texture,
},
},
});
// 设置绘制的高低精度
precision lowp float;
varying vec2 vUv;
varying float vElevation;
uniform sampler2D uTexture;
void main(){
// 根据UV,取出对应的颜色
vec4 textureColor = texture2D(uTexture, vUv);
float letvElevation = vElevation + 0.05 * 10.0;
textureColor.rgb *= letvElevation;
gl_FragColor = textureColor; //vec4(textureColor.rgb, 1.0);
}
需要注意的是,在进行build后图片的路径可能会有所改变导致加载不了图片。
欢迎加群讨论技术,1群:677373950(满了,可以加,但通过不了),2群:656732739

