前端技术, 开发

Shader 学习笔记

基本概念

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

内容目录

PUJI Design 朴及设计 (c) 2024. 沪ICP备17052229号