爱心 ·Love Shader· ▶ 在线运行案例案例合集三维可视化功能案例threehub.cn开源仓库github地址https://github.com/z2586300277/three-cesium-examples400个案例代码:网盘链接你将学到什么ShaderMaterial 自定义着色器实现核心视觉效果THREE.Points 粒子点渲染Canvas 动态纹理贴图GSAP 时间轴与补间动画BufferGeometry 自定义顶点/索引数据requestAnimationFrame渲染循环与resize自适应效果说明本案例演示爱心效果用 Canvas 2D 绘制内容并实时映射为 Three.js 纹理核心用到 ShaderMaterial、THREE.Points、Canvas。建议先打开文首在线案例查看动态画面再对照下方源码逐步理解。核心概念Scene / Camera / WebGLRenderer构成最小渲染闭环大场景可开logarithmicDepthBuffer缓解 Z-fighting。ShaderMaterial通过uniforms 自定义 GLSL 控制逐像素/逐点效果透明粒子常配合depthTest: false。THREE.Points将每个顶点渲染为可控大小的粒子可用自定义 attribute如u_index驱动片元/顶点动画。CanvasTexture每帧或按需把 2D Canvas 内容上传 GPU适合动态文字、图表、视频帧贴图。实现步骤搭建 Scene、PerspectiveCamera、WebGLRenderer挂载 canvas 并处理resize定义 uniforms / onBeforeCompile 或 ShaderMaterial编写 GLSL 与材质参数在定时器或 GSAP 时间轴中更新 uniform / 变换驱动特效播放在requestAnimationFrame循环中更新状态并 renderCesium 为viewer.render或自动渲染代码要点import * as THREE from three;import { gsap } from gsap;class LoveParticlesWorld { constructor() { // 基本参数设置 - 增大粒子数量和默认尺寸 this.params { heartParticles: 1800, starParticles: 700, colorCycle: 0 }; this.initScene(); this.initCamera(); this.initRenderer(); this.createHeart(); this.createStars(); this.createGlow(); this.addEvents(); this.clock new THREE.Clock(); this.animate(); this.addUIElements(); } // 场景初始化 initScene() { this.scene new THREE.Scene(); // 创建更丰富的渐变背景 const bgColors [new THREE.Color(0x1a0033), new THREE.Color(0x000022)]; const bgTexture this.createGradientTexture(bgColors); this.scene.background bgTexture; // 增强光源 this.scene.add(new THREE.AmbientLight(0x404040, 1.8)); this.pointLight new THREE.PointLight(0xff3388, 1.5, 100); this.pointLight.position.set(0, 0, 5); this.scene.add(this.pointLight); } createGradientTexture(colors) { const canvas document.createElement(canvas); canvas.width 2; canvas.height 256; const context canvas.getContext(2d); const gradient context.createLinearGradient(0, 0, 0, 256); gradient.addColorStop(0, colors[0].getStyle()); gradient.addColorStop(1, colors[1].getStyle()); context.fillStyle gradient; context.fillRect(0, 0, 2, 256); const texture new THREE.CanvasTexture(canvas); texture.magFilter THREE.LinearFilter; texture.minFilter THREE.LinearFilter; return texture; } initCamera() { this.camera new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 100 ); this.camera.position.z 4.5; this.scene.add(this.camera); } initRenderer() { this.renderer new THREE.WebGLRenderer({ antialias: true }); this.renderer.setSize(window.innerWidth, window.innerHeight); this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); document.body.appendChild(this.renderer.domElement); } // 创建爱心粒子 - 增大粒子尺寸和发光强度 createHeart() { const heartMaterial new THREE.ShaderMaterial({ fragmentShader:varying vec3 vColor; varying vec2 vUv; varying float vIntensity; void main() { // 更强的发光效果 float strength 1.0 - 2.0 * distance(vUv, vec2(0.5)); strength pow(strength, 1.4); // 更柔和的边缘 // 增强发光和颜色饱和度 vec3 finalColor vColor(1.0 vIntensity0.8); // 更亮的白色光晕 finalColor vec3(1.0)pow(strength, 6.0)0.4; gl_FragColor vec4(finalColor * strength, strength); }, vertexShader:#define PI 3.1415926535897932384626433832795 uniform float uTime; uniform float uSize; attribute float aScale; attribute vec3 aColor; attribute float aSpeed; attribute float aOffset; attribute float aPhase; varying vec3 vColor; varying vec2 vUv; varying float vIntensity; vec3 heartShape(float t, float scale) { t aPhase sin(uTime0.2)0.05; float sign 2.0 * (step(0.5, aOffset) - 0.5); t signmod(-uTimeaSpeed0.004 9.0aSpeed * aSpeed, PI); float a pow(t, 2.0)pow((t - signPI), 2.0); float r 0.16 * scale; // 增大基础大小 // 优化的心形曲线 float x r16.0pow(sin(t), 2.0) * sin(t); float y r(13.0cos(t) - 5.0cos(2.0t) - 2.0cos(3.0t) - cos(4.0 * t)); float z 0.13a(aOffset - 0.5)sin(12.0sin(0.3uTime aOffset3.0) * t); // 更丰富的浮动动画 y sin(uTime0.5)0.06; x cos(uTime0.3)0.04; return vec3(x, y, z); } void main() { vec3 heartPosition heartShape(0.0, 1.0); vec4 modelPosition modelMatrix * vec4(heartPosition, 1.0); vec4 viewPosition viewMatrix * modelPosition; // 更强的脉动效果 float pulse 0.6 0.2sin(uTime(0.6 aOffset0.5) aPhase5.0); float sizeScale aScaleuSizepulse; viewPosition.xyz position * sizeScale; gl_Position projectionMatrix * viewPosition; vColor aColor; vUv uv; vIntensity pulse 0.3sin(uTimeaSpeed * 0.1); }, uniforms: { uTime: { value: 0 }, uSize: { value: 0.38 } // 显著增大粒子默认尺寸 }, transparent: true, blending: THREE.AdditiveBlending, depthWrite: false }); const count this.params.heartParticles; const geometry new THREE.PlaneGeometry(1, 1); const instancedGeometry new THREE.InstancedBufferGeometry(); Object.keys(geometry.attributes).forEach(attr { instancedGeometry.attributes[attr] geometry.attributes[attr]; }); instancedGeometry.index geometry.index; // 创建实例化属性数组 const scales new Float32Array(count); const colors new Float32Array(count * 3); const speeds new Float32Array(count); const offsets new Float32Array(count); const phases new Float32Array(count); // 更鲜艳的配色方案 this.colorSchemes [ // 鲜艳粉红色系 [0xffffff, 0xff1177, 0xff66bb, 0xff44aa, 0xffddee], // 明亮蓝紫色系 [0x99eeff, 0x66aaff, 0x4466ff, 0x9944ff, 0xeeddff], // 金橙黄色系 [0xffdd22, 0xff9911, 0xff6633, 0xffdd99, 0xffbb55] ]; const colorChoices this.colorSchemes[this.params.colorCycle]; for (let i 0; i count; i) { // 增大粒子尺寸范围 scales[i] 0.15 Math.random() * 0.45; speeds[i] 0.5 Math.random() * 4.0; offsets[i] Math.random(); phases[i] Math.random()Math.PI2; const color new THREE.Color(colorChoices[Math.floor(Math.random() * colorChoices.length)]); colors[i * 3] color.r; colors[i * 3 1] color.g; colors[i * 3 2] color.b; } instancedGeometry.setAttribute(aScale, new THREE.InstancedBufferAttribute(scales, 1)); instancedGeometry.setAttribute(aColor, new THREE.InstancedBufferAttribute(colors, 3)); instancedGeometry.setAttribute(aSpeed, new THREE.InstancedBufferAttribute(speeds, 1)); instancedGeometry.setAttribute(aOffset, new THREE.InstancedBufferAttribute(offsets, 1)); instancedGeometry.setAttribute(aPhase, new THREE.InstancedBufferAttribute(phases, 1)); this.heart new THREE.Mesh(instancedGeometry, heartMaterial); this.scene.add(this.heart); } // 简化的星星创建 createStars() { const starsMaterial new THREE.ShaderMaterial({ fragmentShader:varying vec3 vColor; varying float vSize; void main() { float distanceToCenter length(gl_PointCoord - vec2(0.5)); float strength 1.0 - smoothstep(0.0, 0.5, distanceToCenter); vec3 finalColor vColorpow(strength, 1.5)1.5; gl_FragColor vec4(finalColor, strength * 0.8); }, vertexShader:uniform float uTime; attribute float aSize; attribute vec3 aColor; attribute float aSpeed; varying vec3 vColor; varying float vSize; void main() { vec3 pos position; // 闪烁效果 float flicker 0.8 0.5sin(uTimeaSpeed position.x * 100.0); // 轻微漂浮 pos.x sin(uTime0.1aSpeed position.y) * 0.15; pos.y cos(uTime0.1aSpeed position.x) * 0.15; vec4 mvPosition modelViewMatrix * vec4(pos, 1.0); gl_Position projectionMatrix * mvPosition; gl_PointSize aSizeflicker(350.0 / length(mvPosition.xyz)); vColor aColor; vSize flicker; }, uniforms: { uTime: { value: 0 } }, transparent: true, blending: THREE.AdditiveBlending, depthWrite: false }); const starsGeometry new THREE.BufferGeometry(); const starsCount this.params.starParticles; const positions new Float32Array(starsCount * 3); const colors new Float32Array(starsCount * 3); const sizes new Float32Array(starsCount); const speeds new Float32Array(starsCount); const starColors [ new THREE.Color(0xffffff), new THREE.Color(0xaaccff), new THREE.Color(0xffccee), new THREE.Color(0xddddff) ]; for (let i 0; i starsCount; i) { const radius 10 Math.random() * 20; const theta Math.random()Math.PI2; const phi Math.acos(2 * Math.random() - 1); positions[i3] radiusMath.sin(phi) * Math.cos(theta); positions[i3 1] radiusMath.sin(phi) * Math.sin(theta); positions[i3 2] radiusMath.cos(phi) - 15; // 更大的星星 sizes[i] 4 Math.random(); speeds[i] 0.1 Math.random() * 2.0; const color starColors[Math.floor(Math.random() * starColors.length)]; colors[i * 3] color.r; colors[i * 3 1] color.g; colors[i * 3 2] color.b; } starsGeometry.setAttribute(position, new THREE.BufferAttribute(positions, 3)); starsGeometry.setAttribute(aColor, new THREE.BufferAttribute(colors, 3)); starsGeometry.setAttribute(aSize, new THREE.BufferAttribute(sizes, 1)); starsGeometry.setAttribute(aSpeed, new THREE.BufferAttribute(speeds, 1)); this.stars new THREE.Points(starsGeometry, starsMaterial); this.scene.add(this.stars); } // 简化的光晕效果 createGlow() { const glowGeometry new THREE.SphereGeometry(1.8, 32, 32); const glowMaterial new THREE.ShaderMaterial({ fragmentShader:varying float vIntensity; varying vec3 vColor; void main() { float alpha pow(vIntensity, 2.5) * 0.55; gl_FragColor vec4(vColor, alpha); }, vertexShader:uniform float uTime; varying float vIntensity; varying vec3 vColor; void main() { vec3 pos position; float noise sin(pos.x5.0 uTime)sin(pos.y5.0 uTime)sin(pos.z5.0 uTime)0.15; pos normal * noise; vec4 mvPosition modelViewMatrix * vec4(pos, 1.0); gl_Position projectionMatrix * mvPosition; vIntensity 1.0 - length(pos) / 2.0; vColor vec3(0.95, 0.4, 0.7); }, uniforms: { uTime: { value: 0 } }, transparent: true, blending: THREE.AdditiveBlending, side: THREE.BackSide, depthWrite: false }); this.glow new THREE.Mesh(glowGeometry, glowMaterial); this.scene.add(this.glow); } // 简化的事件处理 addEvents() { window.addEventListener(mousemove, (e) { const x e.clientX / window.innerWidth - 0.5; const y e.clientY / window.innerHeight - 0.5; gsap.to(this.camera.position, { x: x * 1.0, // 增大鼠标移动的影响 y: -y * 1.0, duration: 1.0, ease: power2.out }); gsap.to(this.pointLight.position, { x: x * 4, y: -y * 4, duration: 1.5, ease: power1.out }); }); window.addEventListener(click, () { this.params.colorCycle (this.params.colorCycle 1) % this.colorSchemes.length; // 更强的爆炸效果 gsap.to(this.heart.material.uniforms.uSize, { value: 0.5, // 更大的爆炸尺寸 duration: 0.2, ease: power2.out, onComplete: () { gsap.to(this.heart.material.uniforms.uSize, { value: 0.38, duration: 0.5, ease: power2.in }); } }); const colors new Float32Array(this.params.heartParticles * 3); const colorChoices this.colorSchemes[this.params.colorCycle]; for (let i 0; i this.params.heartParticles; i) { const color new THREE.Color(colorChoices[Math.floor(Math.random() * colorChoices.length)]); colors[i * 3] color.r; colors[i * 3 1] color.g; colors[i * 3 2] color.b; } this.heart.geometry.setAttribute( aColor, new THREE.InstancedBufferAttribute(colors, 3) ); const lightColor new THREE.Color(colorChoices[0]); gsap.to(this.pointLight.color, { r: lightColor.r, g: lightColor.g, b: lightColor.b, duration: 1 }); }); window.addEventListener(resize, () { this.camera.aspect window.innerWidth / window.innerHeight; this.camera.updateProjectionMatrix(); this.renderer.setSize(window.innerWidth, window.innerHeight); }); } // 简化的UI元素 addUIElements() { const style document.createElement(style); style.textContent body { margin: 0; overflow: hidden; background: #000; } .info { position: fixed; bottom: 20px; left: 20px; color: rgba(255,255,255,0.9); font-family: Arial, sans-serif; font-size: 16px; background: rgba(0,0,0,0.3); padding: 12px 18px; border-radius: 20px; backdrop-filter: blur(5px); border: 1px solid rgba(255,255,255,0.15); box-shadow: 0 4px 15px rgba(0,0,0,0.3); } .info p { margin: 6px 0; text-shadow: 0 2px 3px rgba(0,0,0,0.8); }; document.head.appendChild(style); const info document.createElement(div); info.className info; info.innerHTML ✨ 移动鼠标改变视角 点击屏幕切换颜色; document.body.appendChild(info); } // 简化的动画循环 animate() { const elapsedTime this.clock.getElapsedTime(); if (this.heart) { this.heart.material.uniforms.uTime.value elapsedTime; this.heart.rotation.y Math.sin(elapsedTime0.2)0.18; this.heart.rotation.z Math.sin(elapsedTime0.1)0.06; } if (this.stars) { this.stars.material.uniforms.uTime.value elapsedTime; this.stars.rotation.y elapsedTime * 0.02; } if (this.glow) { this.glow.material.uniforms.uTime.value elapsedTime; const pulseScale 1 Math.sin(elapsedTime0.8)0.12; this.glow.scale.set(pulseScale, pulseScale, pulseScale); } this.camera.lookAt(this.scene.position); this.renderer.render(this.scene, this.camera); requestAnimationFrame(this.animate.bind(this)); } }// 创建世界 new LoveParticlesWorld();完整源码GitHub小结本文提供爱心完整 Three.js 源码与在线 Demo建议先运行案例再改 uniform/参数做二次实验更多 Three.js 实战案例见 three-cesium-examples 合集 与 GitHub 开源仓库