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! ~~~ | 欢迎路过的小哥哥小姐姐们提出更好的意见哇~~

相关推荐
Navicat中国1 小时前
Navicat 18 内置 AI 助手让数据库开发从繁琐操作迈向智能协作
大数据·人工智能·数据库开发
4SAPI1 小时前
多模型请求路由怎么设计?成本、速度和能力如何平衡
人工智能·python·gateway·php
魔乐社区1 小时前
翼芯・智算测试与适配优化专区,让国产算力“选得准、跑得通、用得好“
人工智能·开源·大模型
知了学历孙老师3 小时前
小自考的校考和统考,出题方和难度差在哪
java·大数据·前端
XCDoris3 小时前
郴州logo设计机构
大数据·人工智能·python
Goboy3 小时前
一个人带三个Agent,我用全新TRAE从0到1搭了个资讯周刊站
人工智能·trae
俊哥V4 小时前
每日 AI 研究简报 · 2026-10-19
人工智能·ai
AI_AGENT_DEV_AI4 小时前
AI英语教培智能体的开发
人工智能
物联网软硬件开发-轨物科技4 小时前
【技术白皮书】光伏清检一体化机器人系统解决方案技术白皮书
大数据·运维·网络·人工智能·机器人
海宇数据4 小时前
零信任架构实战:基于海宇网上踪迹欺诈行为识别构建自动化信贷资信评估网关
运维·人工智能·架构·自动化