心 · Heart Shader · ▶ 在线运行案例
案例合集: 三维可视化功能案例(threehub.cn)
开源仓库github地址: https://github.com/z2586300277/three-cesium-examples
**400个案例代码: ** 网盘链接

你将学到什么
- ShaderMaterial 自定义着色器实现核心视觉效果
- EffectComposer 多 Pass 后期处理管线
- UnrealBloomPass 辉光 Bloom 效果
- OrbitControls 相机轨道交互
requestAnimationFrame渲染循环与resize自适应
效果说明
本案例演示 心 效果:原场景渲染后经 EffectComposer 叠加 Bloom/模糊等全屏后期;核心用到 ShaderMaterial、EffectComposer、UnrealBloomPass。建议先打开文首在线案例查看动态画面,再对照下方源码逐步理解。
核心概念
- Scene / Camera / WebGLRenderer 构成最小渲染闭环;大场景可开
logarithmicDepthBuffer缓解 Z-fighting。 - ShaderMaterial 通过
uniforms+ 自定义 GLSL 控制逐像素/逐点效果;透明粒子常配合depthTest: false。 - EffectComposer 以多 Pass 链式渲染:RenderPass → 特效 Pass → 输出屏幕,替代直接
renderer.render。 - OrbitControls 提供轨道旋转/缩放;开启
enableDamping后需在 animate 中controls.update()。
实现步骤
- 搭建 Scene、PerspectiveCamera、WebGLRenderer,挂载 canvas 并处理
resize - 定义 uniforms / onBeforeCompile 或 ShaderMaterial,编写 GLSL 与材质参数
- 组装 EffectComposer Pass 链,在 animate 中调用
composer.render() - 创建 OrbitControls(及 Raycaster 等交互控件,若源码包含)
- 在
requestAnimationFrame循环中更新状态并 render(Cesium 为viewer.render或自动渲染)
代码要点
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass.js';
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';
const box = document.getElementById('box')
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(75, box.clientWidth / box.clientHeight, 0.1, 1000)
camera.position.set(0, 0, 20)
const renderer = new THREE.WebGLRenderer()
renderer.setSize(box.clientWidth, box.clientHeight)
box.appendChild(renderer.domElement)
new OrbitControls(camera, renderer.domElement)
const composer = new EffectComposer(renderer);
const renderPass = new RenderPass(scene, camera);
composer.addPass(renderPass);
const bloomPass = new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 5, 1.2, 0)
composer.addPass(bloomPass);
window.onresize = () => {
renderer.setSize(box.clientWidth, box.clientHeight)
camera.aspect = box.clientWidth / box.clientHeight
camera.updateProjectionMatrix()
}
class HeartCurve extends THREE.Curve {
constructor(scale = 1) {
super();
this.scale = scale;
}
getPoint(a, optionalTarget = new THREE.Vector3()) {
const t = a * Math.PI * 2;
const tx = 16 * Math.pow(Math.sin(t), 3);
const ty = 13 * Math.cos(t) - 5 * Math.cos(2 * t) - 2 * Math.cos(3 * t) - Math.cos(4 * t);
const tz = 0;
return optionalTarget.set(tx, ty, tz).multiplyScalar(this.scale);
}
}
const geometry = new THREE.TubeGeometry(new HeartCurve(0.5), 100, 0.1, 30, false);
const vertexShader = `
varying vec2 vUv;
void main(void) {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}`;
const fragmentShader = `
varying vec2 vUv;
uniform float uSpeed;
uniform float uTime;
uniform vec2 uFade;
uniform vec3 uColor;
uniform float uDirection;
void main() {
vec3 color =uColor;
float s=0.0;
float v=0.0;
if(uDirection==1.0){
v=vUv.x;
s=-uTime*uSpeed;
}else{
v= -vUv.x;
s= -uTime*uSpeed;
}
float d=mod( (v + s) ,1.0) ;
if(d>uFade.y)discard;
else{
float alpha = smoothstep(uFade.x, uFade.y, d);
if (alpha < 0.0001) discard;
gl_FragColor = vec4(color,alpha);
}
} `;
const getMaterial = (uniforms) => {
return new THREE.ShaderMaterial({
uniforms,
vertexShader,
fragmentShader,
transparent: true
});
}
const material1 = getMaterial({
uColor: { value: new THREE.Color('pink') },
uTime: { value: 0 },
uDirection: { value: 1 },
uSpeed: { value: 1 },
uFade: { value: new THREE.Vector2(0, 0.5) },
uDirection: { value: 1 },
uSpeed: { value: 1 }
})
const material2 = getMaterial({
uColor: { value: new THREE.Color('#00BFFF') },
uTime: { value: 0.5 },
uDirection: { value: 1 },
uSpeed: { value: 1 },
uFade: { value: new THREE.Vector2(0, 0.5) },
uDirection: { value: 1 },
uSpeed: { value: 1 }
})
const mesh = new THREE.Mesh(geometry, material1)
const mesh2 = new THREE.Mesh(geometry, material2)
scene.add(mesh);
scene.add(mesh2)
animate()
function animate() {
material1.uniforms.uTime.value += 0.002
material2.uniforms.uTime.value += 0.002
requestAnimationFrame(animate)
renderer.render(scene, camera)
composer.render();
}
完整源码:GitHub
小结
- 本文提供 心 完整 Three.js 源码与在线 Demo,建议先运行案例再改 uniform/参数做二次实验
- 更多 Three.js 实战案例见 three-cesium-examples 合集 与 GitHub 开源仓库