Three.js进阶:从基础材质到动态着色ShaderMaterial实践

一、基础场景搭建与光照系统配置

在Three.js中创建自定义材质前,需要先构建完整的3D场景环境。以下代码展示了如何初始化渲染器、相机和光照系统:

  1. // 初始化WebGL渲染器(开启抗锯齿)
  2. const renderer = new THREE.WebGLRenderer({ antialias: true });
  3. renderer.setSize(window.innerWidth, window.innerHeight);
  4. renderer.setClearColor(0xfefefe); // 设置浅灰色背景
  5. document.body.appendChild(renderer.domElement);
  6. // 创建透视相机(视野角45度,宽高比1:1,近远裁剪面)
  7. const camera = new THREE.PerspectiveCamera(
  8. 45,
  9. window.innerWidth / window.innerHeight,
  10. 0.1,
  11. 1000
  12. );
  13. // 添加轨道控制器(需要引入OrbitControls)
  14. const orbit = new OrbitControls(camera, renderer.domElement);
  15. camera.position.set(0, -2, 300);
  16. camera.lookAt(0, 0, 0);
  17. // 创建场景并添加环境光
  18. const scene = new THREE.Scene();
  19. const ambientLight = new THREE.AmbientLight(0xa3a3a3, 0.8);
  20. scene.add(ambientLight);
  21. // 添加平行光(模拟太阳光)
  22. const directionalLight = new THREE.DirectionalLight(0xffffff);
  23. directionalLight.position.set(2, 2, 8);
  24. scene.add(directionalLight);

这段代码完成了三个关键设置:

  1. 创建支持抗锯齿的WebGL渲染器
  2. 配置带轨道控制的透视相机
  3. 建立包含环境光和平行光的双光源系统

二、几何体生成与基础材质应用

使用二十面体几何体创建500个随机分布的网格:

  1. // 创建二十面体几何体(半径4,细分级别30)
  2. const geometry = new THREE.IcosahedronGeometry(4, 30);
  3. // 传统物理材质方案(MeshPhysicalMaterial)
  4. const material1 = new THREE.MeshPhysicalMaterial();
  5. // 批量生成网格
  6. for (let i = 0; i < 500; i++) {
  7. const mesh = new THREE.Mesh(geometry, material1);
  8. // 随机位置(-500到500范围)
  9. mesh.position.x = Math.random() * 1000 - 500;
  10. mesh.position.y = Math.random() * 1000 - 500;
  11. mesh.position.z = Math.random() * 1000 - 500;
  12. // 随机缩放(1-4倍)
  13. mesh.scale.setScalar(Math.random() * 3 + 1);
  14. scene.add(mesh);
  15. }

此方案使用物理材质(MeshPhysicalMaterial)虽然能正确响应光照,但存在两个局限:

  1. 材质属性静态,无法根据空间位置变化
  2. 无法实现自定义着色逻辑

三、ShaderMaterial核心实现原理

要实现动态着色效果,需要使用ShaderMaterial。其核心包含三个部分:

1. Uniform变量定义

  1. const uniforms = {
  2. u_resolution: {
  3. value: new THREE.Vector2(window.innerWidth, window.innerHeight)
  4. },
  5. u_time: { value: 0 }, // 添加时间变量用于动画
  6. u_mouse: { value: new THREE.Vector2() } // 可选鼠标交互变量
  7. };

2. 着色器代码结构

在HTML中需要定义两个<script type="x-shader/x-vertex"><script type="x-shader/x-fragment">标签:

  1. <!-- 顶点着色器 -->
  2. <script id="vertexshader" type="x-shader/x-vertex">
  3. varying vec2 vUv;
  4. void main() {
  5. vUv = uv; // 传递UV坐标到片段着色器
  6. gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
  7. }
  8. </script>
  9. <!-- 片段着色器 -->
  10. <script id="fragmentshader" type="x-shader/x-fragment">
  11. uniform vec2 u_resolution;
  12. varying vec2 vUv;
  13. void main() {
  14. vec2 st = gl_FragCoord.xy / u_resolution.xy;
  15. // 基于UV坐标的动态颜色计算
  16. vec3 color = vec3(st.x, st.y, abs(sin(st.x * 10.0 + u_time)));
  17. gl_FragColor = vec4(color, 1.0);
  18. }
  19. </script>

3. 材质创建与网格应用

  1. // 创建ShaderMaterial
  2. const shaderMaterial = new THREE.ShaderMaterial({
  3. uniforms: uniforms,
  4. vertexShader: document.getElementById('vertexshader').textContent,
  5. fragmentShader: document.getElementById('fragmentshader').textContent,
  6. side: THREE.DoubleSide // 双面渲染
  7. });
  8. // 重新生成网格(使用ShaderMaterial)
  9. const meshes = [];
  10. for (let i = 0; i < 500; i++) {
  11. const mesh = new THREE.Mesh(geometry, shaderMaterial);
  12. // ...位置和缩放设置同上...
  13. scene.add(mesh);
  14. meshes.push(mesh);
  15. }

四、动态着色效果实现

要实现基于网格位置的动态着色,需要修改片段着色器:

  1. // 修改后的片段着色器核心逻辑
  2. varying vec3 vPosition; // 从顶点着色器传递的世界坐标
  3. uniform vec3 u_cameraPos;
  4. void main() {
  5. // 计算网格到相机的距离
  6. float dist = distance(vPosition.xyz, u_cameraPos);
  7. // 基于距离和UV坐标的动态颜色
  8. vec3 baseColor = vec3(0.5 + 0.5 * sin(vPosition.x * 0.1),
  9. 0.5 + 0.5 * cos(vPosition.y * 0.1),
  10. 0.5 + 0.5 * sin(vPosition.z * 0.1));
  11. // 添加距离衰减效果
  12. float intensity = smoothstep(100.0, 300.0, dist);
  13. vec3 finalColor = mix(baseColor, vec3(0.2), intensity);
  14. gl_FragColor = vec4(finalColor, 1.0);
  15. }

实现要点:

  1. 通过varying变量传递顶点位置
  2. 使用distance()函数计算空间距离
  3. 应用smoothstep()实现平滑过渡
  4. 使用mix()函数混合基础色和衰减色

五、性能优化与交互增强

1. 动画循环优化

  1. function animate(time) {
  2. // 更新时间变量(毫秒转秒)
  3. shaderMaterial.uniforms.u_time.value = time * 0.001;
  4. // 可选:鼠标位置跟踪
  5. // shaderMaterial.uniforms.u_mouse.value.set(mouseX, mouseY);
  6. renderer.render(scene, camera);
  7. requestAnimationFrame(animate);
  8. }
  9. requestAnimationFrame(animate);

2. 响应式设计

  1. window.addEventListener('resize', () => {
  2. camera.aspect = window.innerWidth / window.innerHeight;
  3. camera.updateProjectionMatrix();
  4. renderer.setSize(window.innerWidth, window.innerHeight);
  5. // 更新分辨率Uniform
  6. shaderMaterial.uniforms.u_resolution.value.set(
  7. window.innerWidth,
  8. window.innerHeight
  9. );
  10. });

3. 调试技巧

  1. 使用THREE.ShaderChunk简化着色器开发
  2. 通过console.log(gl.getShaderInfoLog(shader))调试编译错误
  3. 使用glsl-canvas等工具预览着色器效果

六、典型应用场景

  1. 数据可视化:将数值映射到颜色空间
  2. 动态背景:创建响应式环境效果
  3. 特殊材质:实现卡通渲染、边缘光等效果
  4. 交互设计:根据用户输入改变材质表现

通过掌握ShaderMaterial的开发技术,开发者可以突破Three.js内置材质的限制,实现高度定制化的3D渲染效果。这种基于GPU的编程方式不仅能提升视觉表现力,还能通过并行计算优化性能表现。