基本概念
Shader(着色器)是用来描述顶点和像素如何在 GPU 上处理的程序。每个着色器都包含了两个类型的着色器,分别是顶点着色器 Vertex Shader 和 片段着色器 Fragment Shader.顶点着色器定位几何体的每个顶点坐标。片段着色器用于为几何体的每个可见像素着色。
Attributes
顶点特有的数据,只在顶点着色器中使用。
属性是每个顶点独有的数据,例如顶点的位置、法线和纹理坐标。它们只在顶点着色器中使用,因为它们是与顶点相关的。在着色器代码中,使用 attribute
关键字来声明属性。Three.js 会将这些属性从 JavaScript 传递到 GPU。
attribute vec3 position; // 顶点位置
attribute vec3 normal; // 顶点法线
查看 Attributes
可在js中打印该几何对象的attributes属性来看到所有的attributes
const geometry = new THREE.PlaneBufferGeometry(1, 1, 32, 32);
console.log(geometry.attributes);
自定义 Attributes
const count = geometry.attributes.position.count;
const randoms = new Floate32Array(count);
for(let i = 0; i < count; i++) {
randoms[i] = Math.random();
}
geometry.setAttribute('aRandom', new THREE.BufferAttribute(randoms, 1));
// glsl中调用
attribute float aRandom;
Uniform
全局数据,可以在顶点着色器和片段着色器中使用。
Uniform 是在整个渲染过程中保持不变的全局数据,例如变换矩阵、材质颜色、光源参数等。Uniform 可以在顶点着色器和片段着色器中使用。在着色器代码中,使用 uniform
关键字来声明 Uniform。它们常用于传递相机位置、投影矩阵或光源信息等。
uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;
- 使用同一个着色器,但产生不同的结果
- 可调整这些值
- 可制作动画
- 给uniforms赋值必须使用对象,且为value
modelMatrix, viewMatrix, modelViewMatrix, projectionMatrix 这四个变量是 uniform 类型,由 THREE.JS 自动提供。
modelMatrix
模型矩阵,将相对于网格位置、旋转和缩放对每个顶点应用变换。
// 在js中对模型的位置,旋转和缩放变换的数据THREE.JS会自动转换为模型矩阵
const mesh = new THREE.Mesh(geometry, material);
mesh.position.x = 1;
viewMatrix
关于相机的,包含了相机的位置,旋转,view, near, far 这些数据
modelViewMatrix
将 modelMatrix 与 viewMatrix 两个进行合并的变量,可引用一个这个变量来替代这两个变量。不会对性能作出太大改变
projectionMatrix
投影矩阵会将坐标转换为最终的剪辑空间坐标
自定义 Uniform
同时还可以自定义 uniform 类型的变量,通过 THREE.JS 进行传输
// js 文件
const material = new THREE.ShaderMaterial({
vertexShader,
fragmentShader,
uniforms: {
uFrequency: { value: 10 },
uColor: { value: new THREE.Color('orange') }
uTime: { value: 0 }
},
});
const tick = () => {
const elapsed = clock.getElapsed();
material.uniform.uTime.value = elapsed;
}
// vertexShader
uniform float uFrequency;
uniform float uTime;
// fragmentShader
uniform vec3 uColor;
uniform float uTime;
Varying
在顶点着色器和片段着色器之间传递数据,用于插值处理。
Varying 用于在顶点着色器和片段着色器之间传递数据。顶点着色器计算出某些值后,通过 Varying 传递给片段着色器,片段着色器可以使用这些值进行进一步计算。使用 varying
关键字在顶点着色器中声明并赋值,然后在片段着色器中声明和使用。它们通常用于插值顶点相关数据,如颜色或纹理坐标,以便在片段着色器中进行逐像素处理。
// 在顶点着色器中声明并赋值
attribute float aRandom;
varying float vColor;
void main() {
vColor = aRandom;
// 其他代码...
}
// 在片段着色器中使用
varying float vColor;
void main() {
gl_FragColor = vec4(1.0, vColor, 1.0, 1.0);
}
- 系统会将顶点着色器和片段着色器的数据发送给GPU,让GPU来处理。不会占用CPU的资源。
- 在创建不同类型变量时,名称可增加前缀用于区分,如 aRandom, uPosition, vColor等。
纹理
const textureLoader = new THREE.TextureLoader();
const theTexture = textureLoader.load('./static/image.png');
const material = new THREE.ShaderMaterial({
vertexShader,
fragmentShader,
uniforms: {
uTexture: { value: theTexture }
},
});
// vertexShader
attribute vec2 uv;
varying vec2 vUv;
void main() {
vUv = uv;
}
// framgmentShader
uniform sampler2D uTexture; // 特殊的纹理类型
varying vec2 vUv;
void main() {
vec4 textureColor = texture2D(uTexture, vUv);
gl_FragColor = textureColor;
}
Shader 对象
在 Three.js 中提供了两种类型的 Shader 对象
- ShaderMaterial 将自动添加一些基础代码到着色器中
- RawShaderMaterial 不具任何基础代码
着色器对象中同样也可以使用材质对象中的属性,如 wirefreme, side, transparent, flagShading 等。但 map, alphaMap, opacity, color 等属性将失效,因为这些将在着色器中进行处理。
用处:
- 现有的material无法满足要求
- 绑定多个对象到一个 buffergeometry,已提高性能
注意点
- 只能使用 webglrenderer, vertexshader 和 fragmentshader 代码只能使用 webgl 编译在gpu 上运行
- 必须配套使用 buffergeometry,并且使用bufferattribute定义attributes
- 可以使用 texture property
vertex shader 与 fragmentshader
- vertex shader 首先运行,接受 attributes,计算每个顶点的位置,并把其他数据varying传递到fragment shader
- fragment shader 接着运行,设置每个片段的颜色
RawShaderMaterial
const material = new THREE.RawShaderMaterial({
vertexShader: `
uniform mat4 projectionMatrix;
uniform mat4 viewMatrix;
uniform mat4 modelMatrix;
attribute vec3 position;
void main()
{
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
precision mediump float;
void main()
{
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
`,
});
ShaderMaterial
ShaderMaterial 已初始化了一些基本的变量,无需再额外声明。
const material = new THREE.ShaderMaterial({
vertexShader: `
void main()
{
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
void main()
{
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
`,
});
环境配置
要在项目里通过 import 方法导入 glsl 文件,需在安装一个依赖
npm i @doublewin/glsl-stringify-loader --D
然后在 webpack.config.js 中进行配置
module.exports = {
...
module: {
rules: [
...
{
test: /\.glsl$/,
exclude: [/node_modules/],
use: ['@doublewin/glsl-stringify-loader'],
},
],
},
};
顶点着色器 vertexShader
gl_Position
一个包含顶点在屏幕上位置的变量,该变量已经存在,无需重新声明,是一个四维向量,他将顶点定位在屏幕上的正确位置。
gl_Position = projectedMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);
gl_Position.x += 0.1;
gl_Position.y -= 0.1;
代码示例
uniform mat4 projectionMatrix;
uniform mat4 viewMatrix;
uniform mat4 modelMatrix;
attribute vec3 position;
void main()
{
vec4 modelPosition = modelMatrix * vec4(position, 1.0);
modelPosition.z += sin(modelPosition.x * 10.0) * 0.1;
vec4 viewPostion = viewMatrix * modelPosition;
vec4 projectedPosition = projectionMatrix * viewPostion;
gl_Position = projectedPosition;
}
片段着色器 fragmentShader
gl_FragColor
已存在的一个变量,无需再重新声明。时每个片段上位置的一种颜色。也是一个四维向量。分别是 r, g, b, a
- 使用透明色时需在shader对象中开启 transparent
precision
浮点数的精度,包含 highp, mediump, lowp。精度越高越影响性能,但细节更丰富。默认为mediump。使用ShaderMaterial对象时,three.js会自动处理。
precision mediump float;
void main()
{
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
GLSL 语法
1、不能使用 console.log
进行打印
2、缩紧无关紧要
3、每句代码的末尾必须使用分号断开,一行可以写多句代码,但每句代码结束都需要分号断开。uniform mat4 viewMatrix; uniform mat4 modelMatrix;
4、变量声明必须提供类型
float fooBar = 4.0;
5、浮点数不能与整数一起运算,如需要运算则需将数据类型进行转换,只有相同类型的数据才能进行一起计算
float a = 1.0;
int b = 2;
float c = a * float(b);
数据类型
float 浮点数
float foo = 1.0;
int 整数
int a = 1;
int b = 2;
bool 布尔值
bool foo = true;
vec2 二维向量
// X, Y 均为 1.0
vec2 foo = vec2(1.0);
// X 为 -1.0, Y 为 2.0
vec2 foo = vec2(- 1.0, 2.0);
// 运算,x与y相乘再乘以 2.0
foo *= 2.0 //(结果为4.0)
// 重新赋值
foo.x = 2.0
foo.y = 3.0
vec3 三位向量
可以是 X, Y, Z 或者 R, G, B
// X, Y, Z 均为 1.0
vec3 foo = vec3(1.0);
// X 为 -1.0, Y 为 2.0,Z 为 3.0
vec3 foo = vec3(- 1.0, 2.0, 3.0);
// 通过二维向量数据设置三位向量
vec2 foo = vec2(1.0, 2.0);
vec3 bar = vec3(foo, 3.0);
// 通过三位向量数据设置二维向量 swizzle
vec3 foo = vec3(1.0, 2.0, 3.0);
vec2 a = foo.xy;
vec2 b = foo.xz;
vec2 c = foo.zy;
vec4 四维向量
分别为 X, Y, Z, W 或者 R, G, B, A
vec4 foo = vec4(1.0, 2.0, 3.0, 4.0);
mat2 二维矩阵
mat3 三维矩阵
mat4 四维矩阵
sampler2D
2D采样器,用于纹理等。
运算
支持使用 + – * /,但不支持 %,可用内置函数 mod 代替
函数
// 有返回值
float name(float a, float b) {
return a * b;
}
float result = name(1.0, 2.0);
// 没有返回值,只是一个方法
void name() {
...
}
void main
该函数将自动调用,不会返回任何值。
内置函数
sin, cos, max, min, pow, exp, mod, clamp, cross, dot, mix, step, smoothstep, length, distance, reflect, refract, normalize
mod()
取模
float foo = mod(vUv.y * 10.0, 1.0);
step()
float foo = step(0.5, mod(vUv.y * 10.0, 1.0)); // 大于0.5时为1,否则为0
abs()
绝对值
float foo = abs(vUv.x - 0.5);
min()
最小值
float foo = min(abs(vUv.x - 0.5), abs(vUv.y - 0.5));
max()
最大值
float foo = max(abs(vUv.x - 0.5), abs(vUv.y - 0.5));
round()
四舍五入
floor()
length()
可用于绘制径向渐变
float strength = length(vUv);
distance()
float strength = distance(vUv, vec2(0.5));
atan()
mix()
用于混合颜色
float strength = step(0.9, sin(cnoise(vUv * 10.0) * 20.0));
vec3 blackColor = vec3(0.0);
vec3 uvColor = vec3(vUV, 1.0);
vec3 mixedColor = mix(blackColor, uvColor, strength);
clamp()
float strength = clamp(strength, 0.0, 1.0);
自定义函数
创建自定义函数需在 main 函数前创建。
random()
仅是一个伪随机
float random(vec2 st)
{
return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123);
}
// main 内调用
float foo= random(vUv);
rotate()
用于旋转uv
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
);
}
// main 内调用
vec2 rotatedUv = rotate(vUv, 1.0, vec(0.5));
相关网站
Shaderific 函数的使用方法
Kronos Group registery 针对 OpenGL而非WebGL
Book of Shaders glossary 片段着色器
噪波
https://gist.github.com/patriciogonzalezvivo/670c22f3966e662d2f83
Classic Perlin Noise