Gemini手搓3D粒子特效,体验现实版钢铁侠

无需懂一行代码,Gemini 3正在重塑3D交互创作的边界!详细对比了Canvas与AI Studio在开发场景下的独特优势,带你亲身体验这场「零门槛」的3D交互革命。
无需懂一行代码,Gemini 3正在重塑3D交互创作的边界!详细对比了Canvas与AI Studio在开发场景下的独特优势,带你亲身体验这场「零门槛」的3D交互革命。
Gemini 3的威力其实并没有被很多人真正的意识到。
最近,el.cine演示了如何仅通过简单的提示词,生成一个实时交互的3D粒子系统。

用户可以通过摄像头检测双手张合的手势来直接控制粒子群的缩放和扩散。
用同款提示词在Gemini Canvas试了一下,简直惊掉了下巴。

还可以升级,现在是识别一只手,可以识别两只手,把粒子完全分开。


完全就是AR级别的控制效果,感觉我就是钢铁侠本侠。

提示词非常简单:
提示:使用Three.js创建一个实时交互的3D粒子系统。
要求:
1、通过摄像头检测双手的张合与握紧,控制粒子群的缩放与扩散。
2、提供可选择心形/花朵/土星/佛像/烟花等模板的。
3、面板支持通过颜色选择器调整粒子颜色粒子。
4、需实时响应手势变化,界面设计简洁现代界面设计简洁现代。
整个过程完全无需编程基础,只需在Gemini中输入提示词即可。
这一成果不仅震惊了有经验的程序员(他们曾需花费数周学习GLSL着色器),更标志着复杂3D交互内容的创作门槛被彻底消除,被认为是正在见证的历史性时刻。

而且这个效果,只有Gemini能够「直出」这个效果。
就连谷歌AI Studio自带的Build也不能一次性跑出这么好的结果。

然后Gemini里自带的Canvas,还自带了控制台和代码Fix。
这还要什么Cursor!

第一次跑就这个结果,我以为大家都是这样,然后用了其他几个AI工具。
都没有Gemini跑出来的惊艳。

甚至有些直接就无法使用。

作者原来的例子效果也看起来要好一点。(怀疑是进行了多轮对话以后的结果)

被低估的Canvas
其实很多人还是低估了Canvas的威力了。
比如你想裁剪最近很流行的表情包。
如果直接使用Nano Banana Pro去截图,往往有种「大炮打蚊子」的感觉。
但是如果你先用Canvas做一个截图小工具,甚至还可以选择几行几列的截图,最后还能打包好后下载为zip压缩包。

Canvas是集成在Gemini网页版中的一个功能界面,对标的是OpenAI的Canvas和Anthropic的Artifacts。
核心机制:侧边栏的独立渲染
在传统的ChatGPT界面中,代码和文本是混在对话流里的。如果你想修改一段生成的代码,通常需要「复制-粘贴-修改-再粘贴」。
Canvas的逻辑是:将「对话(Chat)」与「内容(Content)」分离。
左侧:你的对话指令区。
右侧:一个独立的编辑器/渲染器窗口。
Canvas的三大技术杀手锏
这次3D粒子系统之所以能跑通,Canvas的功劳占了一半:
HTML/JS/React实时渲染环境:Canvas就像在浏览器里内置了一个轻量级的IDE。它不仅能显示代码,还能直接运行前端代码。对于WebGL(如Three.js)这种依赖浏览器环境的库,Canvas提供了原生的执行沙箱,这使得调用摄像头(WebcamAPI)变得非常顺滑,而不需要像以前那样把代码复制到本地服务器运行。
针对性的代码修复(FixButton):不同于普通的「重新生成」,Canvas允许你高亮右侧的一段代码或文本,然后告诉AI:「把这里的逻辑改一下」。它只针对局部进行微调,而不是重写全文。这对于调试复杂的3D渲染逻辑至关重要。
多模态的即时反馈:它支持生成图表、网页原型、文档。当你输入「把背景改成黑色」时,右侧的Canvas是实时刷新的,这种低延迟的反馈循环大大降低了试错成本。
适用人群:产品经理、前端设计师、数据分析师、轻量级代码编写者。
被低估的AI Studio
而另一个更被低估的就是AI Studio,AI Studio可以说是开发者的「核武库」。
如果说Canvas是装修精美的样板间,那么谷歌AI Studio就是堆满工具和原材料的施工现场。
它是目前访问Gemini API最快、最直接的路径。
核心定位:模型调试与Prompt Engineering
AI Studio不是为了让你聊天,而是为了让你构建应用。
之前的例子中提到AI Studio的Build功能跑分不如Gemini Canvas,这其实是因为两者的侧重点不同。
AI Studio的硬核特性:
超长上下文(Context Window):这是AI Studio的杀手锏。
它支持Gemini 1.5 Pro的200万token上下文。你可以把整本技术文档、几小时的视频、或者整个项目的代码库直接拖进去。
- 对比Canvas:Canvas的上下文虽然也长,但为了响应速度做了截断和优化;AI Studio则是「生吞」海量数据。
System Instructions(系统指令):在AI Studio里,你可以设置通过「系统指令」来定义模型的角色、语气和输出格式。这比在聊天框里说「你现在是一个xxx」要稳定得多,权重更高。
多模态原生输入:你可以上传一段40分钟的视频,AI Studio不会将其转化为文本字幕,而是直接「看」视频的每一帧。这对于视频理解、音频分析任务是降维打击。
Prompt Caching(提示词缓存):对于开发者来说,这能极大降低API调用成本和延迟。如果你的Prompt有一部分是固定的(比如一本巨大的规则书),AI Studio允许你缓存这部分内容。
适用人群:能够写Python/Node.js的开发者、企业级应用构建者、需要处理海量数据的研究人员。
而且官方给出了很多高质量的Vibe Coding作品库。
案例很多:
- 涵盖了近100多种Vibe Coding作品;
- 包含图像生成、视频创作、游戏开发、文本处理、代码生成等多领域;
- 都是些很惊艳的范例:如漫画生成器、电商产品摄影棚、像素艺术场景生成、YT视频转互动学习应用。

比如可以在网页里玩跑酷。

或者在网页中生成4K高清壁纸。

还有很多例子,都是非常棒的Vibe Coding模板。
不过为什么上面那个粒子效果AI Studio不如Canvas呢?
大概猜一下,这涉及到底层的运行环境策略:
- Canvas的优化目标是「一次性交付」:谷歌对Canvas模式下的模型进行了微调(Fine-tuning),强化了它生成完整、可运行的前端代码的能力,并自动补全了许多 Web 环境下的依赖库。
- AI Studio的环境更「裸」:AI Studio的预览功能主要是为了测试 API 的输出,它的沙箱环境并没有像 Canvas 那样针对流行的前端库(如 Three.js, React, Tailwind)做极致的预加载和兼容性优化。
最后,简单总结就是:
如果你想要一个结果(比如一个能玩的 3D 网页),用Gemini Canvas。
如果你想要基于这个结果开发一个 APP(比如获取它的 API 调用逻辑,集成到你自己的服务器),用AI Studio。
想要体验上述粒子效果可以把下面代码拷贝到HTML单文件中,用浏览器打开即可。
<!DOCTYPEhtml> <htmllang="zh-CN"> <head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width,initial-scale=1.0"> <title>AI手势控制3D粒子系统</title> <!--TailwindCSS--> <scriptsrc="https://cdn.tailwindcss.com"></script> <!--Three.js--> <scriptsrc="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> <!--MediaPipeHands&CameraUtils--> <scriptsrc="https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils/camera_utils.js"crossorigin="anonymous"></script> <scriptsrc="https://cdn.jsdelivr.net/npm/@mediapipe/control_utils/control_utils.js"crossorigin="anonymous"></script> <scriptsrc="https://cdn.jsdelivr.net/npm/@mediapipe/drawing_utils/drawing_utils.js"crossorigin="anonymous"></script> <scriptsrc="https://cdn.jsdelivr.net/npm/@mediapipe/hands/hands.js"crossorigin="anonymous"></script> <style> body{margin:0;overflow:hidden;background-color:#050505;font-family:'SegoeUI',sans-serif;} #canvas-container{position:absolute;top:0;left:0;width:100vw;height:100vh;z-index:1;} #video-preview{ position:absolute; bottom:20px; right:20px; width:160px; height:120px; border-radius:12px; transform:scaleX(-1);/*镜像翻转*/ z-index:10; border:2pxsolidrgba(255,255,255,0.2); background:#000; object-fit:cover; } /*自定义滚动条和面板样式*/ .glass-panel{ background:rgba(20,20,20,0.6); backdrop-filter:blur(12px); -webkit-backdrop-filter:blur(12px); border:1pxsolidrgba(255,255,255,0.1); box-shadow:08px32px0rgba(0,0,0,0.37); } .btn-option{ transition:all0.3sease; } .btn-option:hover,.btn-option.active{ background:rgba(255,255,255,0.2); border-color:rgba(255,255,255,0.5); transform:translateY(-2px); } #loading-overlay{ position:absolute; inset:0; background:#000; z-index:50; display:flex; justify-content:center; align-items:center; color:white; flex-direction:column; transition:opacity0.5s; } </style> </head> <body> <!--LoadingScreen--> <divid="loading-overlay"> <divclass="animate-spinrounded-fullh-16w-16border-t-2border-b-2border-blue-500mb-4"></div> <pclass="text-lgtracking-widesttext-gray-300">正在初始化AI模型与粒子引擎...</p> <pclass="text-xstext-gray-500mt-2">首次加载可能需要几秒钟</p> </div> <!--UIControlPanel--> <divclass="absolutetop-6left-6z-20w-72glass-panelrounded-2xlp-6text-white"> <h1class="text-xlfont-boldmb-1bg-clip-texttext-transparentbg-gradient-to-rfrom-blue-400to-purple-500"> 粒子形态控制器 </h1> <pclass="text-xstext-gray-400mb-6">举起手:握拳收缩,张开扩散</p> <!--ColorPicker--> <divclass="mb-6"> <labelclass="text-xsuppercasetracking-widertext-gray-400mb-2block">粒子颜色</label> <divclass="flexitems-centergap-3"> <inputtype="color"id="color-picker"value="#00ffff" class="w-10h-10roundedcursor-pointerborder-nonebg-transparent"> <spanclass="text-smfont-monotext-gray-300"id="color-code">#00FFFF</span> </div> </div> <!--ShapeTemplates--> <divclass="space-y-3"> <labelclass="text-xsuppercasetracking-widertext-gray-400mb-1block">选择形态</label> <divclass="gridgrid-cols-2gap-3"> <buttonclass="btn-optionactivew-fullpy-2px-3rounded-lgborderborder-white/10text-smtext-leftflexitems-centergap-2"data-shape="heart"> <span>❤️</span>心形 </button> <buttonclass="btn-optionw-fullpy-2px-3rounded-lgborderborder-white/10text-smtext-leftflexitems-centergap-2"data-shape="saturn"> <span>🪐</span>土星 </button> <buttonclass="btn-optionw-fullpy-2px-3rounded-lgborderborder-white/10text-smtext-leftflexitems-centergap-2"data-shape="flower"> <span>🌸</span>莲花 </button> <buttonclass="btn-optionw-fullpy-2px-3rounded-lgborderborder-white/10text-smtext-leftflexitems-centergap-2"data-shape="buddha"> <span>🧘</span>禅定 </button> <buttonclass="btn-optionw-fullpy-2px-3rounded-lgborderborder-white/10text-smtext-leftflexitems-centergap-2"data-shape="fireworks"> <span>🎆</span>烟花 </button> <buttonclass="btn-optionw-fullpy-2px-3rounded-lgborderborder-white/10text-smtext-leftflexitems-centergap-2"data-shape="sphere"> <span>🔮</span>球体 </button> </div> </div> <!--StatusIndicator--> <divclass="mt-6pt-4border-tborder-white/10flexjustify-betweenitems-center"> <spanclass="text-xstext-gray-400">手势状态</span> <divid="gesture-status"class="text-xsfont-boldtext-blue-400">等待摄像头...</div> </div> </div> <!--VideoInput(Hiddenbutusedforprocessing)&Preview--> <videoid="video-input"class="hidden"></video> <canvasid="video-preview"></canvas> <!--3DCanvas--> <divid="canvas-container"></div> <script> //---1.全局配置与状态--- constconfig={ particleCount:15000, particleSize:0.15, baseColor:newTHREE.Color('#00ffff'), lerpSpeed:0.08,//变形速度 handSensitivity:0.1//手势反应速度 }; conststate={ currentShape:'heart', handOpenness:0.5,//0=fist,1=openpalm targetHandOpenness:0.5, isHandDetected:false }; //---2.Three.js初始化--- constcontainer=document.getElementById('canvas-container'); constscene=newTHREE.Scene(); scene.fog=newTHREE.FogExp2(0x050505,0.02); constcamera=newTHREE.PerspectiveCamera(75,window.innerWidth/window.innerHeight,0.1,100); camera.position.z=30; constrenderer=newTHREE.WebGLRenderer({antialias:true,alpha:true}); renderer.setSize(window.innerWidth,window.innerHeight); renderer.setPixelRatio(Math.min(window.devicePixelRatio,2)); container.appendChild(renderer.domElement); //---3.粒子系统核心--- //生成纹理(圆形发光点) functiongetTexture(){ constcanvas=document.createElement('canvas'); canvas.width=32; canvas.height=32; constcontext=canvas.getContext('2d'); constgradient=context.createRadialGradient(16,16,0,16,16,16); gradient.addColorStop(0,'rgba(255,255,255,1)'); gradient.addColorStop(0.2,'rgba(255,255,255,0.8)'); gradient.addColorStop(0.5,'rgba(255,255,255,0.2)'); gradient.addColorStop(1,'rgba(0,0,0,0)'); context.fillStyle=gradient; context.fillRect(0,0,32,32); consttexture=newTHREE.Texture(canvas); texture.needsUpdate=true; returntexture; } constgeometry=newTHREE.BufferGeometry(); constpositions=newFloat32Array(config.particleCount*3); consttargetPositions=newFloat32Array(config.particleCount*3);//目标位置 constrandoms=newFloat32Array(config.particleCount);//用于增加随机扰动 for(leti=0;i<config.particleCount;i++){ positions[i*3]=(Math.random()-0.5)*50; positions[i*3+1]=(Math.random()-0.5)*50; positions[i*3+2]=(Math.random()-0.5)*50; randoms[i]=Math.random(); } geometry.setAttribute('position',newTHREE.BufferAttribute(positions,3)); constmaterial=newTHREE.PointsMaterial({ size:config.particleSize, color:config.baseColor, map:getTexture(), blending:THREE.AdditiveBlending, depthWrite:false, transparent:true, opacity:0.8 }); constparticleSystem=newTHREE.Points(geometry,material); scene.add(particleSystem); //---4.形状生成算法--- constShapes={ sphere:(i)=>{ constr=10; consttheta=Math.random()*Math.PI*2; constphi=Math.acos((Math.random()*2)-1); return{ x:r*Math.sin(phi)*Math.cos(theta), y:r*Math.sin(phi)*Math.sin(theta), z:r*Math.cos(phi) }; }, heart:(i)=>{ //心形参数方程 constscale=0.8; lett=Math.random()*Math.PI*2; //分布优化:在内部填充 constr=Math.sqrt(Math.random())*scale; //使用拒绝采样或简单的层叠来填充体积,这里简化为表面+内部随机 constx=16*Math.pow(Math.sin(t),3); consty=13*Math.cos(t)-5*Math.cos(2*t)-2*Math.cos(3*t)-Math.cos(4*t); constz=(Math.random()-0.5)*4;//一定厚度 //缩放 return{x:x*r,y:y*r,z:z}; }, saturn:(i)=>{ //70%粒子做星球,30%做光环 constisRing=Math.random()>0.6; if(isRing){ constangle=Math.random()*Math.PI*2; //光环半径12-20 constradius=12+Math.random()*8; return{ x:Math.cos(angle)*radius, y:(Math.random()-0.5)*0.5,//很薄 z:Math.sin(angle)*radius }; }else{ //星球本体 constr=7; consttheta=Math.random()*Math.PI*2; constphi=Math.acos((Math.random()*2)-1); return{ x:r*Math.sin(phi)*Math.cos(theta), y:r*Math.sin(phi)*Math.sin(theta), z:r*Math.cos(phi) }; } }, flower:(i)=>{ //莲花/玫瑰形状 constu=Math.random()*Math.PI*2; constv=Math.random()*Math.PI; constr=8*Math.sin(3*u);//花瓣波动 //极坐标转换 constx=r*Math.sin(v)*Math.cos(u); consty=(Math.cos(v)*8)+(Math.sin(u*4)*2);//增加高度上的波动 constz=r*Math.sin(v)*Math.sin(u); return{x,y,z}; }, buddha:(i)=>{ //简单的禅定轮廓模拟(三个球体叠加+底部盘腿环) constrand=Math.random(); letx,y,z; if(rand<0.2){ //头 constr=2.5; consttheta=Math.random()*Math.PI*2; constphi=Math.acos((Math.random()*2)-1); x=r*Math.sin(phi)*Math.cos(theta); y=r*Math.sin(phi)*Math.sin(theta)+7;//向上偏移 z=r*Math.cos(phi); }elseif(rand<0.6){ //身体 constr=4.5; consttheta=Math.random()*Math.PI*2; constphi=Math.acos((Math.random()*2)-1); x=r*Math.sin(phi)*Math.cos(theta); y=r*Math.sin(phi)*Math.sin(theta)+1; z=r*Math.cos(phi); }else{ //腿部/底座(扁球) constr=7; consttheta=Math.random()*Math.PI*2; constphi=Math.acos((Math.random()*2)-1); x=r*Math.sin(phi)*Math.cos(theta); y=(r*Math.sin(phi)*Math.sin(theta))*0.4-4;//压扁并向下 z=r*Math.cos(phi); } return{x,y,z}; }, fireworks:(i)=>{ //爆发状态的初始球体,但在动画循环中会根据手势剧烈扩散 constr=Math.random()*2;//初始很小 consttheta=Math.random()*Math.PI*2; constphi=Math.acos((Math.random()*2)-1); return{ x:r*Math.sin(phi)*Math.cos(theta), y:r*Math.sin(phi)*Math.sin(theta), z:r*Math.cos(phi) }; } }; //计算目标位置 functioncalculateTargets(shapeName){ constgenerator=Shapes[shapeName]||Shapes.sphere; for(leti=0;i<config.particleCount;i++){ constpos=generator(i); targetPositions[i*3]=pos.x; targetPositions[i*3+1]=pos.y; targetPositions[i*3+2]=pos.z; } } //初始生成 calculateTargets('heart'); //---5.MediaPipeHands集成--- constvideoElement=document.getElementById('video-input'); constcanvasElement=document.getElementById('video-preview'); constcanvasCtx=canvasElement.getContext('2d'); conststatusDiv=document.getElementById('gesture-status'); functiononResults(results){ //绘制预览 canvasCtx.save(); canvasCtx.clearRect(0,0,canvasElement.width,canvasElement.height); canvasCtx.drawImage(results.image,0,0,canvasElement.width,canvasElement.height); if(results.multiHandLandmarks&&results.multiHandLandmarks.length>0){ state.isHandDetected=true; constlandmarks=results.multiHandLandmarks[0]; //简单绘制骨架 drawConnectors(canvasCtx,landmarks,HAND_CONNECTIONS,{color:'#00FF00',lineWidth:2}); //计算手掌张开程度 //比较指尖(4,8,12,16,20)到手腕(0)的平均距离 constwrist=landmarks[0]; consttips=[4,8,12,16,20]; lettotalDist=0; tips.forEach(idx=>{ consttip=landmarks[idx]; constd=Math.sqrt(Math.pow(tip.x-wrist.x,2)+Math.pow(tip.y-wrist.y,2)); totalDist+=d; }); constavgDist=totalDist/5; //经验阈值:握拳approx0.15-0.2,张开approx0.4-0.6(取决于距离摄像头的远近) //归一化处理(简单映射) //假设正常距离下,0.2为紧握,0.5为全开 letopenness=(avgDist-0.15)/(0.45-0.15); openness=Math.max(0,Math.min(1,openness)); state.targetHandOpenness=openness; statusDiv.innerText=openness>0.6?"状态:张开(扩散)":(openness<0.3?"状态:握拳(收缩)":"状态:自然"); statusDiv.className=openness>0.6?"text-xsfont-boldtext-red-400":(openness<0.3?"text-xsfont-boldtext-blue-400":"text-xsfont-boldtext-green-400"); }else{ state.isHandDetected=false; //无手势时,缓慢回到默认呼吸状态 state.targetHandOpenness=0.5+Math.sin(Date.now()*0.002)*0.1; statusDiv.innerText="未检测到手势"; statusDiv.className="text-xsfont-boldtext-gray-500"; } canvasCtx.restore(); } consthands=newHands({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); constcameraUtils=newCamera(videoElement,{ onFrame:async()=>{ awaithands.send({image:videoElement}); }, width:320, height:240 }); cameraUtils.start() .then(()=>{ document.getElementById('loading-overlay').style.opacity='0'; setTimeout(()=>document.getElementById('loading-overlay').remove(),500); }) .catch(err=>{ console.error("Cameraaccesserror:",err); document.querySelector('#loading-overlayp').innerText="无法访问摄像头,请检查权限。"; }); //---6.交互逻辑与动画循环--- //UI事件监听 document.querySelectorAll('.btn-option').forEach(btn=>{ btn.addEventListener('click',(e)=>{ //Removeactiveclass document.querySelectorAll('.btn-option').forEach(b=>b.classList.remove('active')); //Addactivetocurrent consttarget=e.currentTarget; target.classList.add('active'); constshape=target.getAttribute('data-shape'); state.currentShape=shape; calculateTargets(shape); }); }); document.getElementById('color-picker').addEventListener('input',(e)=>{ constcolor=newTHREE.Color(e.target.value); material.color=color; document.getElementById('color-code').innerText=e.target.value.toUpperCase(); }); constclock=newTHREE.Clock(); functionanimate(){ requestAnimationFrame(animate); consttime=clock.getElapsedTime(); //平滑手势状态 state.handOpenness+=(state.targetHandOpenness-state.handOpenness)*config.handSensitivity; //粒子更新 constposAttr=geometry.attributes.position; constcurrentPos=posAttr.array; //根据手势计算缩放系数 //0(拳头)->0.2倍收缩 //1(张开)->2.5倍扩散 letexpansionFactor=1.0; if(state.currentShape==='fireworks'){ //烟花模式下,张开手更加夸张 expansionFactor=0.5+state.handOpenness*4.0; }else{ expansionFactor=0.5+state.handOpenness*1.5; } for(leti=0;i<config.particleCount;i++){ constix=i*3; constiy=i*3+1; constiz=i*3+2; lettx=targetPositions[ix]; letty=targetPositions[iy]; lettz=targetPositions[iz]; //施加手势缩放 tx*=expansionFactor; ty*=expansionFactor; tz*=expansionFactor; //增加一点基于噪声的漂浮感(呼吸效果) constnoise=Math.sin(time*2+randoms[i]*10)*(0.2+state.handOpenness); tx+=noise; ty+=noise; tz+=noise; //线性插值移动粒子 currentPos[ix]+=(tx-currentPos[ix])*config.lerpSpeed; currentPos[iy]+=(ty-currentPos[iy])*config.lerpSpeed; currentPos[iz]+=(tz-currentPos[iz])*config.lerpSpeed; } posAttr.needsUpdate=true; //整体旋转 constrotationSpeed=0.005+(state.handOpenness*0.01);//越激动转越快 particleSystem.rotation.y+=rotationSpeed; //如果是土星,稍微倾斜一点 if(state.currentShape==='saturn'){ particleSystem.rotation.z=0.4; }else{ particleSystem.rotation.z*=0.95;//回正 } //粒子大小随手势微调 material.size=config.particleSize*(0.8+state.handOpenness); renderer.render(scene,camera); } //窗口大小调整 window.addEventListener('resize',()=>{ camera.aspect=window.innerWidth/window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth,window.innerHeight); }); animate(); </script> </body> </html>
参考资料:
https://aistudio.google.com/apps
文章来自于“新智元”,作者 “定慧”。
相关主题
读完之后
加入 AINRK 交流群
了解加入交流群的方式,与更多 AI 从业者交流。




