从零开始学习three.js(18):一文详解three.js中的着色器Shader

在WebGL和Three.js的3D图形渲染中,着色器(Shader) 是实现复杂视觉效果的核心工具。通过编写自定义的着色器代码,开发者可以直接操作GPU,实现从基础颜色渲染到动态光照、粒子效果等高级图形技术。本文将深入解析Three.js中着色器的核心概念、实现原理及实战应用,并结合代码示例帮助读者全面掌握这一关键技术。

核心特点​​:


一、着色器基础与分类

1.1 顶点着色器(Vertex Shader)

顶点着色器负责处理几何体的每个顶点,主要功能包括:

  • 顶点位置变换 :将模型空间坐标转换为屏幕空间坐标(通过 gl_Position 输出)

  • 顶点属性计算:如法线变换、纹理坐标传递等

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

说明 :projectionMatrix(投影矩阵)、modelViewMatrix(模型视图矩阵)由Three.js自动注入,position是顶点的原始坐标。

1.2 片元着色器(Fragment Shader)

片元着色器决定每个像素的最终颜色,通过 gl_FragColor 输出:

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

特性:颜色支持动态计算,如基于光照模型、纹理采样等。

1.3 着色器材质类型

Three.js提供两种自定义着色器材质:

  • ​​ShaderMaterial​ ​:简化版材质,内置常用变量(如modelViewMatrix、projectionMatrix),适合快速开发。

  • ​​RawShaderMaterial​ ​:需手动声明所有变量(如attribute、uniform),灵活性更高,适合深度优化。

​​示例代码:创建基础着色器材质​​

复制代码
const material = new THREE.ShaderMaterial({  
  vertexShader: vertexShaderCode,  
  fragmentShader: fragmentShaderCode,  
  uniforms: {  
    time: { value: 0 },  
    color: { value: new THREE.Color(0xff0000) }  
  }  
});  

二、着色器变量类型

2.1 Uniforms

  • 全局常量:所有顶点/片元共享同一值(如时间、光源位置)

  • 传递方式 :通过 ShaderMaterial 的 uniforms 属性设置

    const material = new THREE.ShaderMaterial({
    uniforms: {
    uTime: { value: 0 },
    uTexture: { value: new THREE.TextureLoader().load("texture.png") }
    }
    });

在着色器中声明 :uniform float uTime;

2.2 Attributes

  • 顶点属性:每个顶点独有的数据(如位置、颜色、法线)

  • 典型应用:动态顶点动画(如波浪效果)

    geometry.setAttribute('displacement', new THREE.BufferAttribute(displacementArray, 1));

在顶点着色器中访问 :attribute float displacement;

2.3 Varyings

  • 插值变量:从顶点着色器向片元着色器传递数据(如UV坐标、颜色插值)

    // 顶点着色器
    varying vec2 vUv;
    void main() {
    vUv = uv;
    // ...
    }

    // 片元着色器
    varying vec2 vUv;
    void main() {
    vec4 color = texture2D(uTexture, vUv);
    }

注意:变量名需在两者中保持一致。


三、矩阵变换与坐标系统

3.1 核心矩阵解析

  • 模型矩阵(modelMatrix) :几何体的旋转、平移、缩放变换
  • 视图矩阵(viewMatrix) :相机的观察变换(等同于相机世界矩阵的逆矩阵)
  • 投影矩阵(projectionMatrix) :3D到2D的投影(如透视投影)

3.2 矩阵乘法顺序

顶点变换遵循 "右乘"顺序,确保坐标正确转换:

复制代码
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);

原理:从模型空间→世界空间→相机空间→裁剪空间逐步变换。


四、实战案例:动态波纹效果

4.1 着色器代码实现

顶点着色器(传递UV坐标):

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

片元着色器(动态波纹计算):

复制代码
uniform float uTime;
varying vec2 vUv;

void main() {
    float ripple = sin(length(vUv - 0.5) * 10.0 - uTime * 2.0);
    float opacity = smoothstep(0.0, 0.2, abs(ripple));
    gl_FragColor = vec4(0.2, 0.5, 1.0, opacity);
}

4.2 JavaScript端配置

复制代码
const material = new THREE.ShaderMaterial({
    uniforms: {
        uTime: { value: 0 }
    },
    vertexShader: vertexShaderCode,
    fragmentShader: fragmentShaderCode,
    transparent: true
});

// 动画循环中更新Uniform
function animate() {
    material.uniforms.uTime.value += 0.01;
    requestAnimationFrame(animate);
}

效果:实现以中心向外扩散的蓝色波纹。


五、高级技巧与优化

5.1 性能优化策略

  1. 减少分支语句 :GPU不擅长动态分支,可用 mix() 或 step() 替代 if-else
  2. 向量化运算 :优先使用 vec3/vec4 代替多个 float 计算
  3. 纹理压缩:使用Mipmap和纹理图集减少采样开销

5.2 常见问题排查

  • 变量未声明 :检查Three.js版本是否支持特定语法(如 #version 300 es)
  • 精度问题 :在移动端明确指定精度(precision mediump float;)
  • 矩阵顺序错误:确保模型→视图→投影的乘法顺序正确

六、扩展应用:后期处理通道

通过 EffectComposer 和 ShaderPass 实现屏幕后处理:

复制代码
import { EffectComposer, ShaderPass } from 'three/examples/jsm/postprocessing';

const composer = new EffectComposer(renderer);
composer.addPass(new RenderPass(scene, camera));

const customPass = new ShaderPass(customShaderMaterial);
composer.addPass(customPass);

典型效果:模糊、Bloom光效、颜色校正等。


结语

Three.js着色器为开发者打开了高性能图形编程的大门。通过深入理解GLSL语法、矩阵变换和变量传递机制,可以创造出从基础颜色变化到复杂物理模拟的全方位视觉效果。建议通过Shadertoy平台进行实时演练,结合Three.js文档探索更多可能性。

相关推荐
晗夜未央1 分钟前
任务队列反复失败,先分清重试、阻塞和结果待核对
开发语言·javascript
锋行天下33 分钟前
DeepSeek写Langgraph就是个大傻逼,一本正经胡说八道
前端·人工智能·后端
jktantan1 小时前
每个节点 41 字节:把一棵百万节点树拆进 14 个 typed array
前端·javascript·vue.js·算法·typescript
计算机魔术师1 小时前
Anthropic官方插件被「寄生」,背后是同一类攻击又来了
前端
Y.9991 小时前
电脑黑屏只有鼠标能动
学习
我命由我123452 小时前
访客系统形式(有用户概念、无用户概念)
java·开发语言·前端·javascript·python·产品运营·产品经理
架构师伯子明2 小时前
FFmpeg C++学习(一):编译和demo
开发语言·c++·学习
叼烟扛炮2 小时前
[LangChain] 学习笔记(02):LCEL 链式调用与动态模型配置实战
服务器·开发语言·网络·笔记·学习·langchain
key_3_feng2 小时前
评估与对齐:Agent 自我进化的护栏
前端·数据库·agent
程序员梅雨2 小时前
【从零开始学习数据仓库 | 第二篇】: 拉链表 & 数仓分层
大数据·数据仓库·学习