AI真好玩系列-WebGL爱心粒子手势互动教程 | Interactive Heart Particles with Hand Gestures

AI真好玩系列-WebGL爱心粒子手势互动教程 | Interactive Heart Particles with Hand Gestures

    • [💖 提示词](#💖 提示词)
    • [📚 项目代码](#📚 项目代码)
    • [🌟 项目简介 | Project Introduction](#🌟 项目简介 | Project Introduction)
    • [📌 前提条件 | Prerequisites](#📌 前提条件 | Prerequisites)
    • [🚀 核心技术栈 | Core Technologies](#🚀 核心技术栈 | Core Technologies)
    • [💖 爱心数学公式 | Heart Math Formula](#💖 爱心数学公式 | Heart Math Formula)
    • [👐 手势识别原理 | Gesture Recognition Principle](#👐 手势识别原理 | Gesture Recognition Principle)
    • [🧩 核心代码片段 | Core Code Snippets](#🧩 核心代码片段 | Core Code Snippets)
      • [1. 粒子系统初始化 | Particle System Initialization](#1. 粒子系统初始化 | Particle System Initialization)
      • [2. 手势控制逻辑 | Gesture Control Logic](#2. 手势控制逻辑 | Gesture Control Logic)
      • [3. 粒子动画循环 | Animation Loop](#3. 粒子动画循环 | Animation Loop)
    • [🎨 视觉效果亮点 | Visual Highlights](#🎨 视觉效果亮点 | Visual Highlights)
    • [🛠️ 使用指南 | Run Guide](#🛠️ 使用指南 | Run Guide)
      • [本地运行 | Local Run](#本地运行 | Local Run)
    • [🔧 定制项 | Customization Options](#🔧 定制项 | Customization Options)
    • [🐛 常见问题 | Troubleshooting](#🐛 常见问题 | Troubleshooting)
    • [📚 扩展学习资源 | Extended Resources](#📚 扩展学习资源 | Extended Resources)
    • [Conclusion | 结语](#Conclusion | 结语)

周末的深夜,睡不着觉,闲来无事,打开Gemini哈基米玩一把,废话不多说,先上图看最终效果~

碎碎念:我很想上传视频,但是好像有的博客不支持,这里先放个图片

视频地址:https://www.bilibili.com/video/BV1Q92RBoE6F?t=4.9

💖 提示词

复制代码
创建一个基于WebGL的交互式3D网页,通过摄像头检测用户手掌的张合,实时控制由粒子构成的爱心模型的聚散状态。

📚 项目代码

复制代码
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>WebGL 手势控制爱心粒子</title>
    <style>
        body { margin: 0; overflow: hidden; background-color: #000; }
        #canvas-container { width: 100vw; height: 100vh; position: absolute; top: 0; left: 0; z-index: 1; }
        /* 隐藏摄像头视频流,只用于后台分析 */
        .input_video { display: none; }
        #loading {
            position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
            color: white; font-family: sans-serif; font-size: 24px; pointer-events: none; z-index: 10;
        }
    </style>
    <!-- 引入 Three.js -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
    <!-- 引入 MediaPipe Hands -->
    <script src="https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils/camera_utils.js" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/@mediapipe/control_utils/control_utils.js" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/@mediapipe/drawing_utils/drawing_utils.js" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/@mediapipe/hands/hands.js" crossorigin="anonymous"></script>
</head>
<body>

<div id="loading">正在加载模型与摄像头...<br>请允许摄像头权限</div>
<div id="canvas-container"></div>
<video class="input_video"></video>

<script>
    // --- 1. Three.js 场景初始化 ---
    const scene = new THREE.Scene();
    const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
    camera.position.z = 30;

    const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.getElementById('canvas-container').appendChild(renderer.domElement);

    // --- 2. 创建爱心粒子系统 ---
    const particleCount = 3000; // 粒子数量
    const geometry = new THREE.BufferGeometry();
    const positions = new Float32Array(particleCount * 3);
    const targetPositions = new Float32Array(particleCount * 3); // 存储爱心形状的目标位置
    const randomPositions = new Float32Array(particleCount * 3); // 存储散开时的随机位置

    // 爱心方程函数
    function getHeartPosition(t, scale = 1) {
        const x = 16 * Math.pow(Math.sin(t), 3);
        const y = 13 * Math.cos(t) - 5 * Math.cos(2 * t) - 2 * Math.cos(3 * t) - Math.cos(4 * t);
        const z = 0; 
        return { x: x * scale, y: y * scale, z: z * scale };
    }

    for (let i = 0; i < particleCount; i++) {
        // 生成爱心形状的目标点
        // 为了让爱心立体一点,我们随机分布t,并在Z轴加一点随机扰动
        const t = Math.random() * Math.PI * 2;
        const scale = 0.5; 
        const heartPos = getHeartPosition(t, scale);
        
        // 填充爱心内部 (随机缩放)
        const r = Math.sqrt(Math.random()); 
        
        targetPositions[i * 3] = heartPos.x * r;
        targetPositions[i * 3 + 1] = heartPos.y * r;
        targetPositions[i * 3 + 2] = (Math.random() - 0.5) * 5; // Z轴厚度

        // 生成散开的随机位置 (爆炸效果)
        randomPositions[i * 3] = (Math.random() - 0.5) * 100;
        randomPositions[i * 3 + 1] = (Math.random() - 0.5) * 60;
        randomPositions[i * 3 + 2] = (Math.random() - 0.5) * 50;

        // 初始位置设为散开状态
        positions[i * 3] = randomPositions[i * 3];
        positions[i * 3 + 1] = randomPositions[i * 3 + 1];
        positions[i * 3 + 2] = randomPositions[i * 3 + 2];
    }

    geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));

    // 粒子材质
    const material = new THREE.PointsMaterial({
        color: 0xff69b4, // 热粉色
        size: 0.4,
        transparent: true,
        opacity: 0.8,
        blending: THREE.AdditiveBlending
    });

    const particles = new THREE.Points(geometry, material);
    scene.add(particles);

    // --- 3. 交互逻辑变量 ---
    let gatherFactor = 0; // 0 = 完全散开, 1 = 完全聚合成爱心
    let targetGatherFactor = 0; // 目标聚合度,由手势控制

    // --- 4. MediaPipe Hands 配置 ---
    const videoElement = document.getElementsByClassName('input_video')[0];

    function onResults(results) {
        document.getElementById('loading').style.display = 'none';

        if (results.multiHandLandmarks && results.multiHandLandmarks.length > 0) {
            const landmarks = results.multiHandLandmarks[0];

            // 计算手掌开合程度
            // 简单算法:计算拇指指尖(4)与其他四指指尖(8,12,16,20)到手腕(0)的平均距离
            const wrist = landmarks[0];
            const fingerTips = [4, 8, 12, 16, 20];
            let totalDist = 0;

            fingerTips.forEach(idx => {
                const tip = landmarks[idx];
                const dist = Math.sqrt(
                    Math.pow(tip.x - wrist.x, 2) + 
                    Math.pow(tip.y - wrist.y, 2)
                );
                totalDist += dist;
            });

            const avgDist = totalDist / 5;

            // 经验阈值:
            // 握拳时,指尖距离手腕很近 (avgDist 约 0.1 - 0.2)
            // 张开时,指尖距离手腕较远 (avgDist 约 0.4 - 0.6)
            // 我们做一个映射:握拳(distance small) -> 聚合(factor 1), 张开 -> 散开(factor 0)
            
            // 动态调整这些阈值以适应摄像头的距离
            const closeThreshold = 0.25; 
            const openThreshold = 0.5;

            let normalized = (avgDist - closeThreshold) / (openThreshold - closeThreshold);
            normalized = 1 - Math.min(Math.max(normalized, 0), 1); // 反转:距离越小(握拳),值越大(1)

            targetGatherFactor = normalized; 

        } else {
            // 如果没有检测到手,默认缓慢散开
            targetGatherFactor = 0;
        }
    }

    const hands = new Hands({locateFile: (file) => {
        return `https://cdn.jsdelivr.net/npm/@mediapipe/hands/${file}`;
    }});

    hands.setOptions({
        maxNumHands: 1,
        modelComplexity: 1,
        minDetectionConfidence: 0.5,
        minTrackingConfidence: 0.5
    });

    hands.onResults(onResults);

    const cameraUtils = new Camera(videoElement, {
        onFrame: async () => {
            await hands.send({image: videoElement});
        },
        width: 640,
        height: 480
    });
    cameraUtils.start();

    // --- 5. 动画循环 ---
    function animate() {
        requestAnimationFrame(animate);

        // 粒子自身旋转动画
        particles.rotation.y += 0.002;

        // 平滑过渡聚合系数 (Lerp)
        gatherFactor += (targetGatherFactor - gatherFactor) * 0.05;

        // 更新粒子位置
        const posAttr = particles.geometry.attributes.position;
        const currentPositions = posAttr.array;

        for (let i = 0; i < particleCount; i++) {
            const idx = i * 3;
            
            // 目标位置插值:从 randomPositions 过渡到 targetPositions
            const tx = randomPositions[idx] + (targetPositions[idx] - randomPositions[idx]) * gatherFactor;
            const ty = randomPositions[idx+1] + (targetPositions[idx+1] - randomPositions[idx+1]) * gatherFactor;
            const tz = randomPositions[idx+2] + (targetPositions[idx+2] - randomPositions[idx+2]) * gatherFactor;

            // 增加一点动态浮动效果
            currentPositions[idx] += (tx - currentPositions[idx]) * 0.1;
            currentPositions[idx+1] += (ty - currentPositions[idx+1]) * 0.1;
            currentPositions[idx+2] += (tz - currentPositions[idx+2]) * 0.1;
        }

        posAttr.needsUpdate = true;
        renderer.render(scene, camera);
    }

    animate();

    // 窗口大小调整适配
    window.addEventListener('resize', () => {
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
        renderer.setSize(window.innerWidth, window.innerHeight);
    });
</script>
</body>
</html>

🌟 项目简介 | Project Introduction

这是一个基于 Three.js 和 MediaPipe Hands 的前端交互项目。通过摄像头捕捉你的手势,你可以控制屏幕上的数千个爱心粒子聚拢或散开。握拳时爱心会聚拢成形,张开手掌时则会散落如星辰。是不是超级浪漫又有科技感呢?🥰

📌 前提条件 | Prerequisites

  1. 现代浏览器: Chrome/Firefox/Safari 最新版
  2. 摄像头设备: 内置或外接摄像头
  3. 基础 HTML/CSS/JS 知识: 有助于理解代码结构

🚀 核心技术栈 | Core Technologies

技术 用途 链接
Three.js WebGL 3D 渲染引擎 threejs.org
MediaPipe 手部关键点识别 mediapipe.dev
HTML5 Canvas 粒子渲染容器 -

💖 爱心数学公式 | Heart Math Formula

我们使用经典的爱心参数方程来生成粒子目标位置:

javascript 复制代码
function getHeartPosition(t, scale = 1) {
    const x = 16 * Math.pow(Math.sin(t), 3);
    const y = 13 * Math.cos(t) - 5 * Math.cos(2 * t) - 2 * Math.cos(3 * t) - Math.cos(4 * t);
    return { x: x * scale, y: y * scale, z: 0 };
}

这个方程可以绘制出完美的爱心轮廓,再配合粒子系统就能创造出梦幻的效果啦!💗

👐 手势识别原理 | Gesture Recognition Principle

通过 MediaPipe 获取手部 21 个关键点坐标,我们重点分析以下关键点:

  • 手腕(Wrist): 索引 0
  • 指尖(Finger Tips): 索引 4(拇指), 8(食指), 12(中指), 16(无名指), 20(小指)

计算各指尖到手腕的平均距离,以此判断握拳(爱心聚拢)或张开(爱心散开)状态。

🧩 核心代码片段 | Core Code Snippets

1. 粒子系统初始化 | Particle System Initialization

javascript 复制代码
const particleCount = 3000;
const geometry = new THREE.BufferGeometry();
const positions = new Float32Array(particleCount * 3);
const targetPositions = new Float32Array(particleCount * 3);
const randomPositions = new Float32Array(particleCount * 3);

// 初始化爱心目标位置和随机初始位置...

2. 手势控制逻辑 | Gesture Control Logic

javascript 复制代码
function onResults(results) {
    if (results.multiHandLandmarks && results.multiHandLandmarks.length > 0) {
        const landmarks = results.multiHandLandmarks[0];
        // 计算平均指尖距离并映射到聚合因子...
    } else {
        targetGatherFactor = 0; // 默认散开
    }
}

3. 粒子动画循环 | Animation Loop

javascript 复制代码
function animate() {
    requestAnimationFrame(animate);
    
    // 平滑过渡聚合系数
    gatherFactor += (targetGatherFactor - gatherFactor) * 0.05;
    
    // 更新每个粒子位置(从随机位置插值到爱心形状)
    for (let i = 0; i < particleCount; i++) {
        // 插值计算...
    }
    
    renderer.render(scene, camera);
}

🎨 视觉效果亮点 | Visual Highlights

  1. Additive Blending 加法混合: 粒子重叠时颜色叠加,营造梦幻光晕效果
  2. Smooth Lerp 平滑插值: 粒子移动自然流畅,无突兀感
  3. Dynamic Rotation 动态旋转: 整体粒子云缓慢自旋,增强视觉动感
  4. Responsive Design 响应式设计: 自动适配不同屏幕尺寸

🛠️ 使用指南 | Run Guide

本地运行 | Local Run

  1. 将代码保存为 heart.html
  2. 用现代浏览器直接打开即可(需允许摄像头权限)

🔧 定制项 | Customization Options

项目 修改方法 效果预览
粒子颜色 更改 color: 0xff69b4 💗 粉色爱心 → 💙 蓝色星辰
粒子大小 调整 size: 0.4 🌟 大颗粒 → ⭐ 小星星
粒子数量 修改 particleCount 🌌 稀疏星空 → 💫 浓密银河
形状变换 替换爱心方程 ❤️ 爱心 → 🌸 樱花

🐛 常见问题 | Troubleshooting

  1. 摄像头无法启动?

    • 检查浏览器权限设置
    • 确保没有其他程序占用摄像头
  2. 手势识别不准确?

    • 保持手部在摄像头清晰可见范围内
    • 调整环境光线避免过暗或过曝
  3. 性能卡顿怎么办?

    • 减少粒子数量 (particleCount)
    • 关闭其他占用资源的程序

📚 扩展学习资源 | Extended Resources

Conclusion | 结语

  • That's all for today~ - | 今天就写到这里啦~

  • Guys, ( ̄ω ̄( ̄ω ̄〃 ( ̄ω ̄〃)ゝ See you tomorrow~~ | 小伙伴们,( ̄ω ̄( ̄ω ̄〃 ( ̄ω ̄〃)ゝ我们明天再见啦~~

  • Everyone, be happy every day! 大家要天天开心哦

  • Welcome everyone to point out any mistakes in the article~ | 欢迎大家指出文章需要改正之处~

  • Learning has no end; win-win cooperation | 学无止境,合作共赢

  • Welcome all the passers-by, boys and girls, to offer better suggestions! ~~~ | 欢迎路过的小哥哥小姐姐们提出更好的意见哇~~

相关推荐
whbi1 小时前
DataX Web 部署方案
前端
BD_Marathon1 小时前
【JavaWeb】CSS_三种引入方式
前端·css
excel1 小时前
# Vue 渲染系统的四个关键阶段:从模板编译到新旧 VDOM Patch 的完整机制解析
前端
cos1 小时前
我的 Claude Code 使用小记 2
前端·ai编程·claude
Dreamboat-L1 小时前
ES6 (ECMAScript 2015+)
前端·ecmascript·es6
凤凰战士芭比Q2 小时前
web中间件——(二)Nginx(高级功能、优化)
前端·nginx·中间件
阿珊和她的猫2 小时前
表单数据验证:HTML5 自带属性与其他方法的结合应用
前端·状态模式·html5
谷粒.3 小时前
Cypress vs Playwright vs Selenium:现代Web自动化测试框架深度评测
java·前端·网络·人工智能·python·selenium·测试工具
CareyWYR8 小时前
每周AI论文速递(251201-251205)
人工智能