1. 项目概述
这个项目实现了一个基于React + Three.js + GLSL的Apple 2025动态热成像Logo效果。当用户在网页上移动鼠标或触摸屏幕时,Logo会呈现出类似金属受热后颜色变化的视觉效果,从暖色调的橙黄渐变到冷色调的湛蓝,模拟高温灼烧下的金属表面质感。
这个效果的核心在于:
- 使用Three.js创建3D场景和渲染器
- 通过GLSL着色器实现热成像颜色变换
- 利用帧缓冲对象(FBO)实现离屏渲染
- 结合鼠标交互实现动态效果
- 使用Leva控制面板进行参数调试
2. 技术栈与依赖
2.1 主要技术栈
- React:作为前端框架
- Three.js:3D渲染引擎
- GLSL:着色器语言
- React Three Fiber:Three.js的React封装
- Leva:调试控制面板
2.2 关键依赖解析
javascript复制import {
OrthographicCamera,
DoubleSide,
LinearFilter,
Mesh,
RGBFormat,
RepeatWrapping,
ShaderMaterial,
Texture,
TextureLoader,
VideoTexture
} from "three"
import { Leva, levaStore, useControls } from "leva"
- OrthographicCamera:平行投影相机,适合2D/UI类渲染
- LinearFilter:纹理采样过滤方式,确保纹理缩放时平滑过渡
- ShaderMaterial:自定义着色器材质,实现热成像效果的核心
- VideoTexture:将视频作为纹理源
- Leva:实时调试控制面板
3. 核心实现解析
3.1 场景初始化
javascript复制return (
<Canvas
orthographic
camera={{
position: [0, 0, 1],
left: -2, right: 2,
top: 2, bottom: -2,
near: -1, far: 1
}}
gl={{ antialias: true, alpha: true, outputColorSpace: "srgb" }}
flat
>
<Scene containerRef={containerRef} />
</Canvas>
)
这里使用了正交投影相机,适合2D效果的渲染。相机参数设置确保场景能够完整显示在视口中。
3.2 热力图网格实现
3.2.1 纹理准备
javascript复制// 遮罩纹理
const maskTexture = useLoader(TextureLoader, "/logo.png")
maskTexture.wrapS = maskTexture.wrapT = RepeatWrapping
// 视频纹理
const video = document.createElement("video")
video.src = "/apple.mp4"
video.loop = true
const videoTexture = new VideoTexture(video)
- 遮罩纹理用于定义Logo的形状
- 视频纹理提供基础的热量变化效果
3.2.2 着色器材质
javascript复制const material = new ShaderMaterial({
uniforms: {
blendVideo: { value: 1.0 },
drawMap: { value: drawTexture },
textureMap: { value: videoTexture || maskTexture },
maskMap: { value: maskTexture },
// 各种控制参数...
},
vertexShader: heatVertexShader,
fragmentShader: heatFragmentShader,
transparent: true,
side: DoubleSide,
})
着色器材质是效果实现的核心,它定义了如何将各种纹理和参数组合成最终的热成像效果。
3.3 绘制渲染器实现
3.3.1 双缓冲FBO机制
javascript复制const fboA = useFBO(size, size, fboParams)
const fboB = useFBO(size, size, fboParams)
const renderTargets = {
current: fboA,
previous: fboB
}
使用两个FBO实现双缓冲:
- 前一帧的结果存储在previous FBO中
- 当前帧绘制到current FBO
- 每帧结束后交换两者角色
3.3.2 绘制着色器
glsl复制// draw.frag
void main() {
float dist = distance(pos, uv) / (uRadius.z / uResolution.x);
dist = smoothstep(uRadius.x, uRadius.y, dist);
vec4 color = texture2D(uTexture, uvt + (offset * 0.01));
color *= uFadeDamping;
color.r += offset.x;
color.g += offset.y;
color.b += d * (1.0-dist);
gl_FragColor = vec4(color.rgb, 1.0);
}
这个着色器实现了:
- 根据鼠标位置计算绘制强度
- 应用衰减效果
- 存储绘制结果到FBO
3.4 热成像着色器实现
glsl复制// heat.frag
vec3 gradient(float t) {
float blend1 = smoothstep(p1 - f1 * 0.5, p1 + f1 * 0.5, t);
// 其他blend计算...
vec3 color = color1;
color = mix(color, color2, blend1);
// 其他颜色混合...
return color;
}
void main() {
float heatDraw = draw.b;
float map = video.r;
map = pow(map, power);
vec3 finalColor = gradient(map + heatDraw);
finalColor = saturation(finalColor, 1.3);
gl_FragColor = vec4(finalColor, 1.0);
}
这个着色器实现了:
- 根据输入值计算颜色渐变
- 结合视频纹理和绘制纹理
- 应用饱和度调整
- 输出最终颜色
4. 交互实现
4.1 鼠标追踪
javascript复制const handleDOMPointerMove = (e) => {
const rect = containerRef.current.getBoundingClientRect();
const x = 2 * ((e.clientX - rect.x) / rect.width - 0.5);
const y = 2 * -((e.clientY - rect.y) / rect.height - 0.5);
setMouse([x, y]);
};
将鼠标位置转换为归一化的场景坐标。
4.2 热度计算
javascript复制useFrame((_, delta) => {
if (holdRef.current) {
heatRef.current += heatSensitivity * delta * 60;
heatRef.current = Math.min(1.3, heatRef.current);
} else {
heatRef.current *= heatDecay;
if (heatRef.current < 0.001) heatRef.current = 0;
}
setHeatAmount(heatRef.current);
});
根据鼠标移动计算"热度"值,并实现平滑的衰减效果。
5. 参数调试与控制
使用Leva创建调试面板:
javascript复制const { power, opacity, color1, blend1, fade1 } = useControls("Heat Map", {
power: { value: 1.5, min: 0.1, max: 3 },
opacity: { value: 0.8, min: 0, max: 1 },
// 其他参数...
});
可以实时调整的参数包括:
- 热力强度
- 透明度
- 颜色渐变点
- 混合参数
- 衰减速度等
6. 性能优化技巧
- 使用FBO离屏渲染:减少直接屏幕渲染的开销
- 合理设置纹理参数:
javascript复制texture.minFilter = LinearFilter; texture.magFilter = LinearFilter; - 按需更新:只在参数变化时更新着色器uniforms
- 使用useMemo:避免不必要的重新计算
- 控制渲染分辨率:根据设备性能调整FBO大小
7. 常见问题与解决方案
7.1 效果不显示
- 检查纹理路径是否正确
- 确认视频自动播放属性设置
- 查看控制台是否有WebGL错误
7.2 性能问题
- 降低FBO分辨率
- 减少着色器复杂度
- 限制帧率
7.3 颜色异常
- 检查颜色空间设置
- 确认纹理格式
- 验证着色器计算
8. 扩展思路
-
响应式设计:根据屏幕尺寸调整参数
javascript复制useEffect(() => { const aspect = size.width / size.height; // 调整相机参数... }, [size]); -
多点触控支持:扩展交互方式
-
预设效果:保存和加载参数组合
-
动态纹理:使用实时生成的纹理替代视频
这个项目展示了如何结合现代Web技术创建引人注目的视觉效果。通过Three.js和GLSL的强大能力,我们可以在浏览器中实现接近原生应用的图形效果。