Three.js着色器怎么写?JavaScript三维渲染入门到进阶实战指南

来源:Nodejs教程作者:上海网站建设头衔:草根站长
导读:本期聚焦于上海网站建设创作的《Three.js着色器怎么写?JavaScript三维渲染入门到进阶实战指南》,敬请观看详情。写Three.js项目时总绕不开着色器,可不少教程一上来就贴一大段GLSL代码,看得人云里雾里。着色器到底是什么,顶点着色器和片元着色器各自负责什么,uniform和varying变量怎么传数据,这些基础问题搞不明白,后面写自定义材质就是照猫画虎。本指南从WebGL渲染管线讲起,一步步拆解Three.js中着色器的执行流程,带你用原生ShaderMaterial写出第一个会动的图形,再逐步实现光照、噪声纹理、后期处理等常见效果。文中对比了内置材质与自定义着色器的适用场景,整理了调试GLSL代码的实用技巧和常见报错排查方法,适合有一定JavaScript基础、想深入三维渲染的开发者系统学习。

Three.js封装了WebGL的大量底层细节,让开发者用几行代码就能把一个立方体渲染到浏览器里。但当你想实现水面波光、全息投影、溶解消失这类特效时,内置材质就不够用了,必须自己写着色器。着色器是运行在GPU上的小程序,理解它的工作方式是JavaScript开发者迈向三维渲染进阶的关键一步。本文将从渲染管线入手,带你系统掌握Three.js着色器的编写方法。

Three.js着色器怎么写?JavaScript三维渲染入门到进阶实战指南

一、理解渲染管线:着色器到底在哪一步工作

先厘清概念。GPU渲染一帧画面的过程大致是这样的:CPU端准备好顶点坐标、法线、UV等数据,连同材质参数一起提交给GPU;GPU先对每个顶点执行顶点着色器,算出它在屏幕上的投影位置;随后光栅化阶段把三角形内部插值成一个个像素点;最后对每个像素执行片元着色器,决定这个像素最终是什么颜色。两个着色器分工明确:前者管形状和位置,后者管颜色和质感。

这解释了为什么着色器代码看起来和JavaScript完全不同。它用的是GLSL语言,语法接近C,没有垃圾回收,没有动态类型,变量必须在开头声明,循环边界必须是编译期常量。刚开始写会觉得很受限,但这些限制换来了极高的并行执行效率——GPU可以同时跑成千上万个片元着色器实例,这正是它能实时渲染复杂画面的原因。

Three.js中的材质体系是分层的。MeshBasicMaterial、MeshStandardMaterial这些内置材质内部其实也是着色器,Three.js只是帮你拼好了GLSL代码。而ShaderMaterialRawShaderMaterial则把控制权交给你:前者会自动注入投影矩阵、模型视图矩阵等常用变量,适合大多数场景;后者什么都不注入,你面对的是最原始的WebGL环境,一般只在需要精细控制或对接其他渲染引擎时使用。初学阶段直接用ShaderMaterial即可。

二、动手写第一个着色器:流动的渐变球体

下面这个例子渲染一个球体,颜色随时间在表面流动。代码虽短,但包含了着色器开发的核心要素:uniform变量从JavaScript向GPU传递全局数据,varying变量在顶点和片元着色器之间插值传递,内置的projectionMatrixmodelViewMatrix由Three.js自动提供。

const material = new THREE.ShaderMaterial({
  // 传入时间 uniform,每帧更新
  uniforms: {
    uTime: { value: 0 }
  },
  vertexShader: `
    varying vec3 vPosition;
    void main() {
      // 把顶点局部坐标传给片元着色器,GPU会自动插值
      vPosition = position;
      // 投影到裁剪空间,这是顶点着色器的固定职责
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
  `,
  fragmentShader: `
    uniform float uTime;
    varying vec3 vPosition;
    void main() {
      // 用正弦函数让颜色随时间和位置流动
      float r = sin(vPosition.x * 3.0 + uTime) * 0.5 + 0.5;
      float g = sin(vPosition.y * 3.0 + uTime * 1.3) * 0.5 + 0.5;
      float b = sin(vPosition.z * 3.0 + uTime * 0.7) * 0.5 + 0.5;
      gl_FragColor = vec4(r, g, b, 1.0);
    }
  `
});

const mesh = new THREE.Mesh(new THREE.SphereGeometry(1, 64, 64), material);
scene.add(mesh);

// 渲染循环中更新时间
function animate() {
  material.uniforms.uTime.value = clock.getElapsedTime();
  renderer.render(scene, camera);
  requestAnimationFrame(animate);
}

逐个看关键点。uniforms是CPU与GPU之间唯一的动态数据通道,类型必须是{ value: xxx }这种对象结构,每帧直接修改value即可,不需要重新编译着色器。varying声明的变量在顶点着色器中赋值后,片元着色器拿到的是三角形三个顶点之间插值后的结果,这是理解很多视觉效果(比如法线过渡、UV贴图)的基础。

还要注意GLSL的严格性:浮点数必须带小数点,写1会报错,要写1.0vec3乘以标量时类型必须匹配;版本不同的GLSL语法略有差异,Three.js默认使用GLSL ES 1.0,如果你想用in/out这类新版语法,需要先了解GLSL 3.0的写法规则,避免混用导致编译失败。

三、实现光照与法线:从平面色块到立体质感

上面的球体虽然颜色花哨,但看起来像个贴图气球,没有立体感。原因是没有光照计算。要在着色器里实现漫反射,需要用到顶点法线。Three.js的内置属性里自带normal,但要注意:法线是在局部坐标系下定义的,当物体发生旋转缩放时,必须用normalMatrix变换到视图空间,否则光照方向会错。

vertexShader: `
  varying vec3 vNormal;
  varying vec3 vViewPosition;
  void main() {
    // normalMatrix 是模型视图矩阵的逆转置,专用于变换法线
    vNormal = normalize(normalMatrix * normal);
    vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
    vViewPosition = mvPosition.xyz;
    gl_Position = projectionMatrix * mvPosition;
  }
`,
fragmentShader: `
  varying vec3 vNormal;
  varying vec3 vViewPosition;
  void main() {
    // 简单的方向光漫反射
    vec3 lightDir = normalize(vec3(1.0, 2.0, 1.5));
    float diff = max(dot(normalize(vNormal), lightDir), 0.0);
    // 环境光 + 漫反射
    vec3 color = vec3(0.15, 0.15, 0.2) + vec3(0.7, 0.8, 0.9) * diff;
    gl_FragColor = vec4(color, 1.0);
  }
`

如果想让场景里的光源跟随Three.js的场景图(比如你加了DirectionalLight),可以改用onBeforeCompile方案:保留内置材质,只往它的着色器里注入一小段自己的代码。这样既能复用Three.js成熟的光照和阴影系统,又能实现自定义效果,是很多特效的实际做法,比从零写ShaderMaterial省事得多。

四、调试技巧与常见坑

写着色器最大的痛苦是出错时只有黑屏,没有任何提示。分享几个实用排查手段。第一,GLSL编译错误会打印在浏览器控制台,Three.js会把出错的着色器代码带行号一起输出,看到一长串代码不要慌,翻到最后通常有具体的错误信息。第二,怀疑某个变量有问题时,直接把它输出成颜色:gl_FragColor = vec4(vec3(vNormal * 0.5 + 0.5), 1.0)能把法线可视化,法线正确的物体表面会呈现蓝紫色调,这是图形开发中最经典的调试手法。

几个高频踩坑点也列一下:uniform类型写错不会报错但会静默失效,比如GLSL里声明vec3但传入的是普通数组,要改用THREE.Vector3THREE.Color;贴图必须用THREE.Texture类型,采样时用texture2D;精度问题上,片元着色器默认是highp,但某些移动设备只支持mediump,做大场景计算时注意数值漂移;另外gl_FragColor是GLSL 1.0的写法,如果你看到别人的代码用out vec4 fragColor,那是3.0版本,不要直接照抄。

掌握这些基础后,就可以往噪声函数、菲涅尔边缘光、屏幕后期处理这些方向延伸了。着色器的学习曲线前期陡峭,但核心心智模型就一句话:顶点着色器决定像素在哪,片元着色器决定像素是什么颜色,所有视觉效果都是围绕这两点做数学文章。先把本文的例子跑起来,改改参数看效果变化,比读十篇教程都有用。

Three.js着色器WebGL修改时间:2026-09-08 10:19:04

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/20260908/52705.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。