自定义WebGL着色器

在Three.js开发中,自定义WebGL着色器是突破内置材质限制、实现高度定制化渲染效果的核心技术,尤其适合GIS数据可视化、复杂视觉特效、高性能数据渲染等场景。以下是WebGL自定义着色器的完整使用指南,结合Three.js的实践场景展开:


一、基础概念:Three.js与WebGL着色器

WebGL着色器分为顶点着色器(Vertex Shader)片元着色器(Fragment Shader),分别负责顶点的空间变换和像素颜色计算。Three.js通过ShaderMaterialRawShaderMaterial封装了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的EffectComposerShaderPass
  • 自定义片元着色器处理全屏纹理
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值

四、性能优化建议

  1. 减少片元着色器计算:将复杂计算移到顶点着色器(如光照计算)
  2. 避免分支语句:GLSL分支会降低GPU并行效率,用数学表达式替代if-else
  3. 纹理复用:将多个小数据合并为一张纹理(如速度场+温度场)
  4. LOD(细节层次):根据相机距离切换不同复杂度的着色器
  5. 批量渲染:合并相同材质的几何体,减少Draw Call

总结

自定义WebGL着色器是Three.js开发的核心进阶技能,尤其在GIS数字孪生、大规模数据可视化、高端视觉特效场景中不可或缺。通过掌握GLSL语法、Three.js着色器材质的使用方法,结合实际场景的数学模型(如大气散射、高斯模糊),可以实现远超内置材质的定制化效果,满足复杂的业务需求。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值