在Three.js开发中,自定义WebGL着色器是突破内置材质限制、实现高度定制化渲染效果的核心技术,尤其适合GIS数据可视化、复杂视觉特效、高性能数据渲染等场景。以下是WebGL自定义着色器的完整使用指南,结合Three.js的实践场景展开:
一、基础概念:Three.js与WebGL着色器
WebGL着色器分为顶点着色器(Vertex Shader)和片元着色器(Fragment Shader),分别负责顶点的空间变换和像素颜色计算。Three.js通过ShaderMaterial或RawShaderMaterial封装了WebGL的底层细节,同时提供了大量内置变量/函数,简化着色器开发。
1. 两种核心材质对比
| 材质类型 | 特点 | 适用场景 |
|---|---|---|
ShaderMaterial | 自动注入Three.js内置uniform(如相机矩阵、光照参数)、支持内置ShaderChunk复用 | 需复用Three.js光照/变换逻辑的场景 |
RawShaderMaterial | 完全自定义,无内置注入,需手动定义所有uniform/attribute | 完全独立的底层渲染(如自定义管线) |
2. 着色器核心语法(GLSL ES 100)
- 顶点着色器:输入顶点数据(位置、UV、法线等),输出裁剪空间坐标及传递给片元着色器的varying变量
- 片元着色器:输入varying变量、uniform全局参数,输出像素颜色
- Uniforms:全局常量(如时间、纹理、相机参数),可在JavaScript中动态更新
- Attributes:每个顶点唯一的数据(如顶点颜色、自定义权重)
- Varyings:顶点着色器传递到片元着色器的插值变量(如UV、法线)
二、Three.js中自定义着色器的基本使用步骤
1. 编写着色器代码(GLSL字符串)
// 顶点着色器:将顶点位置转换到裁剪空间,传递UV给片元着色器
const vertexShader = `
varying vec2 vUv;
void main() {
vUv = uv; // 传递内置UV属性到片元着色器
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); // 标准空间变换
}
`;
// 片元着色器:实现随时间变化的渐变色
const fragmentShader = `
uniform float uTime; // 全局时间参数
varying vec2 vUv;
void main() {
// 基于UV和时间生成渐变颜色
vec3 color = vec3(
sin(vUv.x * 10.0 + uTime) * 0.5 + 0.5,
sin(vUv.y * 10.0 + uTime) * 0.5 + 0.5,
sin(uTime) * 0.5 + 0.5
);
gl_FragColor = vec4(color, 1.0); // 输出像素颜色
}
`;
2. 创建ShaderMaterial并配置参数
import * as THREE from 'three';
// 定义Uniforms:JavaScript与着色器的通信桥梁
const uniforms = {
uTime: { value: 0 }, // 初始时间为0
};
// 创建自定义材质
const customMaterial = new THREE.ShaderMaterial({
vertexShader,
fragmentShader,
uniforms,
transparent: false, // 是否开启透明
side: THREE.DoubleSide, // 双面渲染
});
// 应用到网格
const geometry = new THREE.PlaneGeometry(2, 2);
const mesh = new THREE.Mesh(geometry, customMaterial);
scene.add(mesh);
3. 动态更新Uniforms(关键)
在渲染循环中更新uniforms,实现动态效果:
function animate() {
requestAnimationFrame(animate);
// 更新时间参数(毫秒转秒)
customMaterial.uniforms.uTime.value = performance.now() * 0.001;
renderer.render(scene, camera);
}
animate();
三、典型应用场景(重点结合GIS/数据可视化)
1. GIS地形与高程可视化
场景:基于DEM(数字高程模型)数据实现自定义地形着色、坡度/坡向渲染
实现思路:
- 将DEM数据作为顶点高度属性传递给着色器
- 顶点着色器计算坡度/坡向,传递给片元着色器
- 片元着色器根据高度/坡度映射颜色
// 顶点着色器:计算坡度(简化版)
attribute float uElevation; // 每个顶点的高程值
varying float vSlope;
void main() {
// 计算相邻顶点的高度差,近似坡度
float slope = abs(uElevation - neighborElevation) / distance;
vSlope = slope;
// 顶点位置偏移:基于高程修改Y轴
vec3 pos = position;
pos.y = uElevation * 0.1; // 缩放高程
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
}
2. 矢量瓦片自定义符号化
场景:GIS矢量瓦片(点、线、面)的自定义渲染(如动态线宽、渐变填充、点符号缩放)
实现思路:
- 将矢量数据转为
BufferGeometry,传递顶点类型、属性等自定义attribute - 片元着色器根据顶点类型实现不同的符号化逻辑
- 结合相机缩放级别(uniform)实现自适应渲染
// 片元着色器:动态线宽(基于相机距离)
uniform float uZoom; // 地图缩放级别
varying float vLineWidth;
void main() {
// 线宽随缩放级别自适应:缩放越大,线越宽
float lineWidth = vLineWidth * pow(2.0, uZoom - 10.0);
// 实现线的抗锯齿渲染
float distanceToCenter = abs(gl_PointCoord.x - 0.5) * 2.0;
float alpha = smoothstep(lineWidth, lineWidth - 0.1, distanceToCenter);
gl_FragColor = vec4(0.2, 0.6, 1.0, alpha);
}
3. 热力图/密度可视化
场景:海量点数据的密度热力图(如人口分布、GPS轨迹)
实现思路:
- 将点数据的权重作为attribute传递给着色器
- 片元着色器计算高斯模糊后的密度值
- 颜色映射:将密度值映射为热力颜色(蓝→绿→黄→红)
// 片元着色器:高斯模糊密度计算
uniform sampler2D uTexture; // 点数据纹理
uniform vec2 uResolution; // 屏幕分辨率
void main() {
vec2 uv = gl_FragCoord.xy / uResolution;
// 采样周围像素计算密度
float density = 0.0;
for (int i = -2; i <= 2; i++) {
for (int j = -2; j <= 2; j++) {
vec2 offset = vec2(i, j) / uResolution * 0.01;
density += texture2D(uTexture, uv + offset).r * 0.1;
}
}
// 颜色映射:使用HSL转RGB
vec3 color = hsl2rgb(vec3(0.1 - density * 0.8, 1.0, 0.5));
gl_FragColor = vec4(color, density * 0.8);
}
4. 动态流线可视化
场景:风场、水流、交通流的动态流线渲染
实现思路:
- 将速度场数据作为纹理或uniform传递给着色器
- 顶点着色器根据时间和速度场更新顶点位置
- 片元着色器实现流线的渐变颜色和透明度
// 顶点着色器:流线运动
uniform float uTime;
uniform sampler2D uVelocityField; // 速度场纹理(RG通道为XY方向速度)
varying float vAge; // 流线年龄
void main() {
vec2 uv = position.xz * 0.5 + 0.5; // 转换为UV坐标
vec2 velocity = texture2D(uVelocityField, uv).rg * 2.0 - 1.0; // 速度归一化
vec3 pos = position;
pos.xz += velocity * uTime * 0.1; // 根据时间更新位置
vAge = mod(uTime + position.y, 10.0); // 计算流线年龄
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
}
5. 三维地球大气散射
场景:模拟地球的大气散射效果,增强真实感(GIS数字孪生场景必备)
实现思路:
- 片元着色器计算光线穿过大气的路径长度
- 瑞利散射和米氏散射的数学模型实现
- 结合相机位置和太阳方向动态调整
// 片元着色器:简化版大气散射
uniform vec3 uSunDirection;
uniform vec3 uCameraPosition;
vec3 atmosphericScattering(vec3 rayOrigin, vec3 rayDirection) {
// 计算光线与大气的交点
float rayLength = length(rayDirection);
vec3 rayPos = rayOrigin + rayDirection * rayLength;
// 瑞利散射:短波长(蓝)散射更强
float rayleigh = exp(-rayLength * 0.1) * 0.5;
// 米氏散射:长波长(红)散射更强
float mie = exp(-rayLength * 0.5) * 0.3;
return vec3(rayleigh * 0.8, rayleigh * 0.6 + mie * 0.4, mie * 0.9);
}
void main() {
vec3 rayDirection = normalize(gl_FragCoord.xyz - uCameraPosition);
vec3 color = atmosphericScattering(uCameraPosition, rayDirection);
gl_FragColor = vec4(color, 1.0);
}
6. 后期处理特效
场景:全局屏幕特效(如Bloom模糊、景深、色阶调整)
实现思路:
- 使用Three.js的
EffectComposer和ShaderPass - 自定义片元着色器处理全屏纹理
import { EffectComposer, ShaderPass } from 'three/addons/postprocessing/EffectComposer.js';
// 自定义后期处理着色器:灰度效果
const grayShader = {
uniforms: {
tDiffuse: { value: null }, // 输入纹理
},
vertexShader: `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
uniform sampler2D tDiffuse;
varying vec2 vUv;
void main() {
vec4 color = texture2D(tDiffuse, vUv);
float gray = dot(color.rgb, vec3(0.299, 0.587, 0.114));
gl_FragColor = vec4(vec3(gray), color.a);
}
`,
};
// 创建后期处理通道
const composer = new EffectComposer(renderer);
composer.addPass(new RenderPass(scene, camera));
composer.addPass(new ShaderPass(grayShader));
// 渲染循环中使用composer替代renderer
function animate() {
requestAnimationFrame(animate);
composer.render();
}
三、进阶技巧与最佳实践
1. 复用Three.js内置ShaderChunk
Three.js将常用的光照、变换逻辑封装为ShaderChunk,可直接引入着色器:
// 顶点着色器中引入皮肤动画计算
#include <skinning_pars_vertex>
void main() {
#include <skinbase_vertex>
#include <skinnormal_vertex>
#include <begin_vertex>
gl_Position = projectionMatrix * modelViewMatrix * vec4(transformed, 1.0);
}
2. 修改现有材质的着色器(onBeforeCompile)
无需完全自定义材质,可扩展Three.js内置材质(如MeshStandardMaterial):
const material = new THREE.MeshStandardMaterial({ color: 0xffffff });
material.onBeforeCompile = (shader) => {
// 添加自定义uniform
shader.uniforms.uCustomTexture = { value: customTexture };
// 修改片元着色器,混合自定义纹理
shader.fragmentShader = shader.fragmentShader.replace(
'vec4 diffuseColor = vec4( diffuse, opacity );',
'vec4 customColor = texture2D(uCustomTexture, vUv);\n' +
'vec4 diffuseColor = vec4( mix(diffuse, customColor.rgb, customColor.a), opacity );'
);
};
3. 着色器调试工具
- 使用Three.js官方扩展
ShaderEditor实时编辑着色器 - 片元着色器中输出中间值:
gl_FragColor = vec4(debugValue, 0, 0, 1); - 浏览器WebGL Inspector:查看着色器编译错误、uniform值
四、性能优化建议
- 减少片元着色器计算:将复杂计算移到顶点着色器(如光照计算)
- 避免分支语句:GLSL分支会降低GPU并行效率,用数学表达式替代
if-else - 纹理复用:将多个小数据合并为一张纹理(如速度场+温度场)
- LOD(细节层次):根据相机距离切换不同复杂度的着色器
- 批量渲染:合并相同材质的几何体,减少Draw Call
总结
自定义WebGL着色器是Three.js开发的核心进阶技能,尤其在GIS数字孪生、大规模数据可视化、高端视觉特效场景中不可或缺。通过掌握GLSL语法、Three.js着色器材质的使用方法,结合实际场景的数学模型(如大气散射、高斯模糊),可以实现远超内置材质的定制化效果,满足复杂的业务需求。
4212

被折叠的 条评论
为什么被折叠?



