Three.js 着色器相关方法总结

文章目录

Three.js 里和着色器相关的内容,主要围绕这几类:

  • ShaderMaterial
  • RawShaderMaterial
  • onBeforeCompile
  • uniforms
  • attributes
  • varying
  • shader chunks
  • 后期处理 shader pass
  • WebGLRenderTarget

ShaderMaterial

ShaderMaterial 是 Three.js 里"自己写 GPU 渲染逻辑"的材质

普通材质,比如:

  • MeshBasicMaterial
  • MeshStandardMaterial
  • PointsMaterial

都是 Three.js 帮你写好了 shader。

ShaderMaterial 是你自己提供:

  • vertexShader
  • fragmentShader
  • uniforms
  • 透明、混合、双面渲染等渲染配置

基本用法

javascript 复制代码
const material = new THREE.ShaderMaterial({
  uniforms: {
    uTime: { value: 0 },
    uColor: { value: new THREE.Color(0x7df7ff) },
  },
  vertexShader: `
    varying vec2 vUv;

    void main() {
      vUv = uv;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
  `,
  fragmentShader: `
    varying vec2 vUv;
    uniform float uTime;
    uniform vec3 uColor;

    void main() {
      gl_FragColor = vec4(uColor, 1.0);
    }
  `,
  transparent: true,
  depthWrite: false,
  side: THREE.DoubleSide,
  blending: THREE.AdditiveBlending,
});

uniforms

uniforms 是 THREE.ShaderMaterial 里用来把 JavaScript 侧的数据传进 GLSL 着色器的参数。

简单说:它是 CPU 传给 GPU shader 的"外部变量表"。

  • uTime:可以每帧更新,用来做动画
  • uColor:传颜色给片元着色器
  • uTexture:传贴图给 shader
javascript 复制代码
uniforms: {
  uTime: { value: 0 },
  uInnerColor: { value: new THREE.Color(0x7df7ff) },
}

shader 里接收:

javascript 复制代码
uniform float uTime;
uniform vec3 uInnerColor;

每帧更新:

javascript 复制代码
material.uniforms.uTime.value = seconds;

常见用途:

  • 时间动画
  • 颜色
  • 纹理
  • 鼠标位置
  • 分辨率
  • 速度、强度、透明度等参数

vertexShader

vertexShader 是顶点着色器。

它会对几何体的每个顶点执行一次。

主要负责:

  • 计算顶点最终位置
  • 修改顶点动画
  • 设置 gl_Position
  • 把数据传给 fragmentShader

常见写法:

javascript 复制代码
varying vec2 vUv;

void main() {
  vUv = uv;
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}

几个关键变量:

position

几何体自带的顶点位置。

uv

几何体自带的纹理坐标,通常范围是 0 ~ 1

modelViewMatrix

模型矩阵和相机视图矩阵的组合。

projectionMatrix

相机投影矩阵。

gl_Position

顶点最终在屏幕裁剪空间中的位置,必须赋值。

fragmentShader

fragmentShader 是片元着色器。

可以简单理解成:它决定每个像素最终显示的颜色。

常见写法:

javascript 复制代码
varying vec2 vUv;
uniform vec3 uColor;

void main() {
  gl_FragColor = vec4(uColor, 1.0);
}

输出颜色:

javascript 复制代码
gl_FragColor = vec4(r, g, b, a);

其中:

  • r
  • g
  • b
  • a

范围通常是 0.0 ~ 1.0

例如:

javascript 复制代码
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);

表示不透明红色。

varying

varying 用来把 vertexShader 里的数据传给 fragmentShader。是自定义的中转变量

顶点着色器里:

javascript 复制代码
varying vec2 vUv;

void main() {
  vUv = uv;
}

片元着色器里:

javascript 复制代码
varying vec2 vUv;

void main() {
  gl_FragColor = vec4(vUv.x, vUv.y, 1.0, 1.0);
}

vUv 会在三角形表面自动插值。

也就是说,fragment shader 得到的不是某个单独顶点的 uv,而是当前像素位置对应的插值 uv。

transparent

javascript 复制代码
transparent: true

开启透明渲染。

如果你的 fragmentShader 里输出了 alpha:

javascript 复制代码
gl_FragColor = vec4(color, alpha);

那通常需要设置:

javascript 复制代码
transparent: true

否则透明度可能不会按预期显示。

适合:

  • 光环
  • 粒子
  • 能量面
  • 玻璃
  • 半透明 UI 面板

depthWrite

javascript 复制代码
depthWrite: false

表示这个材质不写入深度缓冲。

适合半透明和发光材质。

如果透明物体写入深度,可能出现:

  • 前面的透明面挡住后面的透明面
  • 粒子出现黑块
  • 发光层次断裂
  • 后方透明物体不显示

所以粒子、光环、传送门常用:

javascript 复制代码
depthWrite: false

side

javascript 复制代码
side: THREE.DoubleSide

表示正反两面都渲染。

常见取值:

javascript 复制代码
THREE.FrontSide

只渲染正面,默认值。

javascript 复制代码
THREE.BackSide

只渲染背面。

javascript 复制代码
THREE.DoubleSide

正反面都渲染。

传送门的圆面、能量面、平面特效通常会用:

javascript 复制代码
side: THREE.DoubleSide

因为从背后或侧面看时,也希望它可见。

blending

javascript 复制代码
blending: THREE.AdditiveBlending

设置颜色混合方式。

AdditiveBlending 是加法混合。

效果是:颜色叠加后会越来越亮。

适合:

  • 发光
  • 火焰
  • 粒子
  • 星星
  • 能量环
  • 激光
  • 魔法特效

普通透明混合更像"半透明塑料片"。

加法混合更像"光"。

attributes

attribute 是每个顶点自己的数据。

Three.js 内置常见 attribute:

  • position
  • normal
  • uv
  • color

自定义 attribute:

javascript 复制代码
const count = 1000;
const sizes = new Float32Array(count);

for (let i = 0; i < count; i++) {
  sizes[i] = Math.random() * 10;
}

geometry.setAttribute('aSize', new THREE.BufferAttribute(sizes, 1));

shader 中使用:

javascript 复制代码
attribute float aSize;

void main() {
  gl_PointSize = aSize;
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}

适合:

  • 每个粒子的尺寸
  • 每个顶点的随机值
  • 每个点的速度
  • 每个点的相位
  • 每个实例的参数

注意:

attributevertexShader 中使用,不能直接在 fragmentShader 中使用。

如果片元阶段也需要它,要通过 varying 传过去。

ShaderMaterial 渲染流程

javascript 复制代码
JavaScript
  创建 geometry
  创建 ShaderMaterial
  设置 uniforms
  每帧更新 uniforms

vertexShader
  每个顶点执行一次
  计算顶点位置
  传递 varying 数据

fragmentShader
  每个像素执行一次
  根据 uv、颜色、时间等计算最终颜色

renderer
  根据 transparent / depthWrite / side / blending 等配置
  把结果画到 canvas

ShaderMaterial 是 Three.js 里自定义视觉效果的入口。

它让你自己决定:

  • 顶点怎么动
  • 像素怎么上色
  • 透明怎么处理
  • 发光怎么叠加
  • 动画参数怎么传入 GPU

普通材质适合常规物体。

ShaderMaterial 适合:

  • 粒子
  • 波纹
  • 能量环
  • 水面
  • 火焰
  • 扫描线
  • 传送门
  • 自定义后期视觉效果

RawShaderMaterial

RawShaderMaterialShaderMaterial 更底层。

区别是:

ShaderMaterial 会帮你注入 Three.js 的内置变量、宏和 shader chunk。

RawShaderMaterial 不会自动注入太多东西,你需要自己写得更完整。

基本写法:

javascript 复制代码
const material = new THREE.RawShaderMaterial({
  uniforms: {
    uColor: { value: new THREE.Color(0xff0000) },
  },
  vertexShader: `
    precision mediump float;

    uniform mat4 projectionMatrix;
    uniform mat4 modelViewMatrix;

    attribute vec3 position;

    void main() {
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
  `,
  fragmentShader: `
    precision mediump float;

    uniform vec3 uColor;

    void main() {
      gl_FragColor = vec4(uColor, 1.0);
    }
  `,
});

适合:

  • 更接近原生 WebGL 的写法
  • 想完全控制 shader 输入
  • 学习底层 WebGL shader
  • 避免 Three.js 自动注入内容

一般项目里优先用 ShaderMaterial,除非你明确需要更底层控制。

onBeforeCompile

onBeforeCompile 可以修改 Three.js 内置材质的 shader。

它适合这种场景:

你想保留 MeshStandardMaterial 的光照、阴影、PBR、贴图能力,但又想插入一点自定义 shader 逻辑。

例如给标准材质加一个时间 uniform:

javascript 复制代码
const material = new THREE.MeshStandardMaterial({
  color: 0xffffff,
  roughness: 0.4,
  metalness: 0.2,
});

material.onBeforeCompile = (shader) => {
  shader.uniforms.uTime = { value: 0 };

  shader.vertexShader = shader.vertexShader.replace(
    '#include <common>',
    `
      #include <common>
      uniform float uTime;
    `,
  );

  shader.vertexShader = shader.vertexShader.replace(
    '#include <begin_vertex>',
    `
      #include <begin_vertex>
      transformed.y += sin(position.x * 4.0 + uTime) * 0.2;
    `,
  );

  material.userData.shader = shader;
};

每帧更新:

javascript 复制代码
if (material.userData.shader) {
  material.userData.shader.uniforms.uTime.value = elapsedTime;
}

适合:

  • 基于标准材质做轻微变形
  • 保留灯光和阴影
  • 给 PBR 材质加扫描线、波动、溶解等效果
  • 不想从零写完整 shader

GLSL 常用内置变量

顶点着色器和片元着色器的区别

在 Three.js / WebGL 里,一个物体最终显示到屏幕上,大致会经过两个核心阶段:

  1. 顶点着色器 vertexShader
  2. 片元着色器 fragmentShader

它们运行的位置、次数、职责都不一样。

顶点着色器 vertexShader

顶点着色器是处理"顶点"的。

几何体里有多少个顶点,顶点着色器就大致执行多少次。

比如一个平面有 4 个顶点:

javascript 复制代码
const geometry = new THREE.PlaneGeometry(1, 1);

顶点着色器主要处理这 4 个顶点的位置。

顶点着色器最重要的任务是输出:

javascript 复制代码
gl_Position

也就是当前顶点最终在屏幕上的位置。

典型写法:

javascript 复制代码
void main() {
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}

顶点着色器适合做:

  • 顶点位置变形
  • 波浪起伏
  • 粒子位置变化
  • 点粒子大小
  • uv / color / normal 等数据传给片元着色器

例如让平面上下起伏:

javascript 复制代码
uniform float uTime;

void main() {
  vec3 transformed = position;
  transformed.y += sin(position.x * 4.0 + uTime) * 0.2;

  gl_Position = projectionMatrix * modelViewMatrix * vec4(transformed, 1.0);
}

片元着色器 fragmentShader

片元着色器是处理"片元"的。

可以先粗略理解成:处理屏幕上的每个像素。

一个三角形被投影到屏幕后,会覆盖很多像素区域。

这些像素区域里的每个片元,都会执行片元着色器。

片元着色器最重要的任务是输出颜色:

javascript 复制代码
gl_FragColor

典型写法:

javascript 复制代码
void main() {
  gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}

表示输出红色。

片元着色器适合做:

  • 颜色
  • 透明度
  • 贴图采样
  • 渐变
  • 波纹
  • 噪声
  • 扫描线
  • 发光边缘
  • 溶解效果
  • 圆形粒子裁剪

例如根据 uv 做渐变:

javascript 复制代码
varying vec2 vUv;

void main() {
  gl_FragColor = vec4(vUv.x, vUv.y, 1.0, 1.0);
}

总结:

顶点着色器:

bash 复制代码
处理顶点
决定位置
决定形状怎么变
执行次数相对少
必须输出 gl_Position

片元着色器:

bash 复制代码
处理片元/像素
决定颜色
决定透明、贴图、图案
执行次数通常很多
输出 gl_FragColor

为什么叫片元着色器,而不是颜色着色器

片元是什么

片元不是最终像素,但很接近像素。

渲染流程大概是:

javascript 复制代码
顶点
  ↓
三角形
  ↓
光栅化
  ↓
片元 fragments
  ↓
片元着色器
  ↓
深度测试 / 模板测试 / 混合
  ↓
最终像素

光栅化阶段会把三角形覆盖到屏幕上的区域切成很多小候选单位。

这些候选单位就叫:

javascript 复制代码
fragment / 片元
为什么不直接叫 pixel

因为片元还不一定会成为最终像素。

一个片元后面还可能经历:

  • 深度测试:被前面的物体挡住,就不会显示
  • 模板测试:不符合 stencil 条件就丢弃
  • discard:shader 内主动丢弃
  • blending:和已有颜色混合
  • MSAA 多重采样:一个像素里可能有多个采样片元

所以片元着色器输出的是:

javascript 复制代码
这个候选片元应该是什么颜色、透明度、深度等

但它还不是屏幕最终结果。

为什么不叫颜色着色器

因为 fragment shader 不只决定颜色,还可以处理:

  • 透明度
  • 丢弃片元 discard
  • 贴图采样
  • 法线贴图
  • 阴影
  • 光照结果
  • 雾效
  • 深度相关效果
  • 屏幕空间坐标
  • 多渲染目标输出
  • 后期 pass 中的整屏像素处理

比如:

javascript 复制代码
if (alpha < 0.01) {
  discard;
}

这不是"上色",而是直接让这个片元不存在。

再比如:

javascript 复制代码
vec4 texColor = texture2D(uTexture, vUv);
gl_FragColor = texColor;

它是在决定这个片元从纹理哪里采样、透明度是多少、最后怎么参与混合。

更准确的理解
javascript 复制代码
fragment shader = 片元着色器

它处理的是"光栅化后生成的片元"。

颜色只是它最常见、最直观的输出之一。

顶点着色器里的内置输入

内置变量的特点是:

它们不是你自己定义的变量名,而是 GLSL/WebGL 已经规定好的名字。

gl_VertexID

WebGL2 中可用。

表示当前顶点的编号。

javascript 复制代码
int id = gl_VertexID;

用途:

  • 根据顶点编号生成图案
  • 不依赖 attribute 生成简单几何逻辑

WebGL1 中通常不能用。

顶点着色器里的内置输出

gl_Position

顶点着色器最重要的内置输出变量。

它表示当前顶点最终在裁剪空间中的位置。

javascript 复制代码
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);

必须赋值。

如果不赋值,GPU 不知道这个顶点应该画到哪里。

类型:

javascript 复制代码
vec4

含义:

javascript 复制代码
gl_Position = vec4(x, y, z, w);

gl_PointSize

只在渲染点粒子时常用。

它控制当前点的屏幕尺寸。

javascript 复制代码
gl_PointSize = 8.0;

用于:

  • THREE.Points
  • 粒子
  • 星点
  • 火花
  • 雪花

类型:

javascript 复制代码
float

如果你渲染的是 Mesh,通常用不到它。

片元着色器里的内置输入

gl_FragCoord

表示当前片元在屏幕上的坐标。

javascript 复制代码
vec2 screenUv = gl_FragCoord.xy / uResolution;

类型:

javascript 复制代码
vec4

常用:

javascript 复制代码
gl_FragCoord.x
gl_FragCoord.y
gl_FragCoord.z
gl_FragCoord.w

常见用途:

  • 屏幕空间效果
  • 后期处理
  • 像素扫描线
  • 根据屏幕坐标生成图案

gl_PointCoord

只在绘制 Points 时有意义。

表示当前片元在点精灵内部的位置。

范围通常是:

javascript 复制代码
0.0 ~ 1.0

比如把方形点裁成圆形:

javascript 复制代码
float d = distance(gl_PointCoord, vec2(0.5));

if (d > 0.5) {
  discard;
}

gl_FragColor = vec4(1.0);

类型:

javascript 复制代码
vec2

用于:

  • 圆形粒子
  • 粒子贴图
  • 星点光斑
  • 火花效果

gl_FrontFacing

表示当前片元来自正面还是背面。

类型:

javascript 复制代码
bool

示例:

javascript 复制代码
if (gl_FrontFacing) {
  gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
} else {
  gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}

用途:

  • 正反面不同颜色
  • 调试法线方向
  • 双面材质效果

片元着色器里的内置输出

gl_FragColor

WebGL1 常用。

表示当前片元最终输出颜色。

javascript 复制代码
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);

类型:

javascript 复制代码
vec4

四个分量:

javascript 复制代码
vec4(r, g, b, a)

范围通常是:

javascript 复制代码
0.0 ~ 1.0

注意:

在 WebGL2 / GLSL 300 es 中,通常不用 gl_FragColor,而是自定义输出变量。

自定义片元输出变量

WebGL2 / GLSL 300 es 写法。

javascript 复制代码
out vec4 outColor;

void main() {
  outColor = vec4(1.0, 0.0, 0.0, 1.0);
}

Three.js 默认很多示例仍然是 WebGL1 风格,所以经常看到:

javascript 复制代码
gl_FragColor = vec4(...);

片元着色器里的特殊指令

discard

discard 不是变量,是内置指令。

它表示丢弃当前片元,不绘制这个像素。

javascript 复制代码
if (alpha < 0.01) {
  discard;
}

用途:

  • 裁剪透明边缘
  • 做圆形粒子
  • 溶解效果
  • mask 效果

Three.js 中最常见的内置变量组合

普通 Mesh

顶点着色器:

javascript 复制代码
void main() {
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}

片元着色器:

javascript 复制代码
void main() {
  gl_FragColor = vec4(1.0);
}

核心内置变量:

  • gl_Position
  • gl_FragColor

Points 粒子

顶点着色器:

javascript 复制代码
void main() {
  vec4 viewPosition = modelViewMatrix * vec4(position, 1.0);

  gl_Position = projectionMatrix * viewPosition;
  gl_PointSize = 8.0;
}

片元着色器:

javascript 复制代码
void main() {
  float d = distance(gl_PointCoord, vec2(0.5));

  if (d > 0.5) {
    discard;
  }

  gl_FragColor = vec4(1.0);
}

核心内置变量:

  • gl_Position
  • gl_PointSize
  • gl_PointCoord
  • gl_FragColor
  • discard

最常用内置变量速查

名称 阶段 类型 作用
gl_Position vertexShader vec4 当前顶点最终位置
gl_PointSize vertexShader float 点粒子的屏幕尺寸
gl_FragCoord fragmentShader vec4 当前片元的屏幕坐标
gl_PointCoord fragmentShader vec2 点粒子内部坐标
gl_FrontFacing fragmentShader bool 当前片元是否来自正面
gl_FragColor fragmentShader vec4 当前片元输出颜色
discard fragmentShader 指令 丢弃当前片元
相关推荐
aini_lovee9 小时前
MATLAB 基于多层编码遗传算法的车间调度优化
开发语言·matlab
Maimai108089 小时前
前端如何落地 SSE:从实时评论到可复用的实时数据 Hook
前端·javascript·react.js·前端框架·web3·状态模式·webassembly
AI人工智能+电脑小能手9 小时前
【大白话说Java面试题 第63题】【JVM篇】第23题:工作中用过的JVM常用基本配置参数有哪些?
java·开发语言·jvm·面试
吃好睡好便好9 小时前
在Matlab中绘制二维直方图
开发语言·人工智能·学习·算法·matlab
June bug9 小时前
(Mac)torch==2.1.2 与 Python 3.12 不兼容+onnxruntime-silicon 不支持 Intel Mac
开发语言·python·macos
AI科技星9 小时前
全域粒子质量几何曲率统一公式体系(通俗易懂版)
c语言·开发语言·网络·量子计算·agi
周末也要写八哥9 小时前
C++变参模板之空参包的特殊情况
java·开发语言·c++
爱炸薯条的小朋友9 小时前
C#由窗体原子表溢出造成的软件闪退,根本原因补充
开发语言·c#·wpf
冴羽9 小时前
JavaScript 9 个先有库再有 API 的故事
前端·javascript