news 2026/10/20 3:14:15

WebGL实战:3D圆球年会抽奖工具完整开发与现场排障

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
WebGL实战:3D圆球年会抽奖工具完整开发与现场排障

说实话,我第一次在公司年会上看到“3D圆球年会抽奖软件”跑起来的时候,心里想的是:这不就是把名单撒在一个球上转吗?但真正坐在操作间里看完第一轮抽奖,我才意识到它和大转盘、PPT抽号码完全不是一类东西。球体转起来,全场几百号人的名字绕着球面翻滚,所有人都会下意识抬头找自己的名字。停止的瞬间,灯光聚焦在一个人身上,现场气氛真的能起来。

我后来利用业余时间自己做了一个 Windows 可执行版,完整走了一遍从需求梳理、技术选型、核心功能实现到现场排障的过程,也用它实际撑过了两场年会。今天把这套经验完整拆开讲一遍,给准备做年会互动工具、或者想拿 WebGL 练手的开发者一个可以照抄的参考。

1. 为什么我要给一个抽奖工具做 3D 圆球

1.1 常见抽奖方案的现场差距

先说背景。大部分公司的年会抽奖,用的无非是这么几套:

  • 网页抽奖平台:主持人打开网页,点一下“开始”,屏幕滚动名字,再点一下出结果。优点是方便,缺点是一旦年会场地网络不行,就直接抓瞎。我见过不止一次,大屏已经显示“正在连接服务器”,台下几百号人一起等转圈的尴尬场面。
  • PPT 或 Excel 做随机数:优点是零成本,缺点是视觉上极其单薄,更像是在核对名单,而不是在“发奖”。尤其当奖项比较大时,全场没有一个集中视觉的焦点,气氛很容易冷。
  • 大转盘:互动感不错,但只适合把奖品做成选项,不适合把几百个参与者的名字全部体现出来。转盘能承载的信息量很有限。
  • 微信摇一摇:参与度高,但需要每个人都掏手机、连 WiFi,还会把现场互动变成“手机屏幕互动”,大屏反而成了配角。

这些方案本质上都满足“抽一个人出来”这个结果,但缺一样东西:视觉张力。年会抽奖表面上是一个随机过程,实际上是一场舞台表演。观众要看的是“全场所有人的名字都在一个空间里滚动,最后由系统选出一个焦点”,这种“所有人都在球上、所有人都有机会”的视觉隐喻,是平面列表滚动给不了的。

1.2 从现场需求倒推软件功能

我给自己列了一个需求清单,不追求大而全,只求在现场好用:

需求项具体要求
参与规模300 到 1000 人,名单可滚动导入
运行环境Windows 单机,全程离线可跑
名单来源Excel / CSV,兼容部门、工号、姓名等常见列
抽奖规则多轮抽奖,已中奖者不再进入后续抽奖池
大屏效果3D 球体旋转,名字清晰可见,停止时有聚焦动画
操作方式快捷键启动/停止,不需要主持人碰鼠标
结果留存每轮中奖名单自动记录,方便后续发奖核对

从中能看到一个核心矛盾:**现场电脑性能参差不齐,但视觉要求又不能太低。**所以后面所有技术选型,都要在这个前提下做决定。

1.3 明确不做什么

我也给自己划了底线:不做在线账号体系,不做微信联动,不做云端存储,不做多人在线互动。原因很简单,年会现场最不可控的因素就是网络。任何时候联网都可能成为故障源,离线单机才是低风险方案。后续再想加互动,也是在局域网内部做,而不是依赖公网。

2. 技术选型复盘:三套原型实测后的取舍

2.1 选型标准先定下来

我选技术栈时,主要看五件事:**开发速度、视觉效果上限、部署体积、现场电脑能否撑住、后续维护难度。**年会软件要求开发周期短,可能只有两三个晚上;同时它又要在别人公司的旧电脑上稳定运行,不能要求对方有独立显卡。这两个条件一叠加,就淘汰掉很多看起来很炫的方案。

我当时实际测过三套:

方案开发速度视觉上限部署体积低配置电脑表现维护成本
原生桌面 UI 加 3D 控件较慢中小好代码量多
大型 3D 引擎快极高大配置要求偏高工程重
Chromium 内核加 WebGL快高中等可控低成本

2.2 为什么最后选了 Chromium 加 WebGL 这套方案

很多人一听到 3D,第一反应是用大型 3D 引擎。我也试过,但建一个场景、调一套 UI、再打包成 Windows 安装包,整个工程重了不少,为了年会抽奖这个场景有点杀鸡用牛刀。它的渲染能力确实强,但体积和内存占用放到现场电脑上并不友好。

我最后用的是Chromium 桌面壳 + WebGL 3D 库。原因很实际:

  • WebGL 社区里现成的球体、粒子、灯光、文字标签案例非常多,我可以直接改,不需要从零写矩阵变换。
  • 操作界面用 CSS 就能做,比在 3D 引擎里拼 UI 快得多。现场操作员的控制台、中奖名单面板、按钮状态,这些本来就是典型的前端页面。
  • 资源全部本地加载,不依赖网络。打出来的包可以做成绿色版,拷到 U 盘就能跑。
  • 出问题的时候可以直接开调试工具看性能数据,查起来比黑盒式渲染要直观。

有人会担心 WebGL 在旧电脑上跑不动,我实测下来,只要把纹理尺寸和粒子数量控制好,核显电脑也能维持流畅。这一块后面有专门章节讲优化。

2.3 技术栈清单

具体环境是:Node.js 做桌面壳,WebGL 3D 库做渲染,原生 Canvas 做名字贴图,再用 Web Audio API 处理音效。名单解析用一个开源的表格解析库,能直接读 xlsx 和 csv。打包用 electron-builder 的 portable 模式,产出一个免安装 exe。

这套组合的意义在于:**界面、逻辑、渲染全部用同一种语言体系,改起来没有技术栈切换成本。**年会前一晚突然要增加一个“特别奖”按钮,我也能在十分钟内改完重新打包。

3. 圆球表面、名单翻滚与抽奖公平性的实现细节

3.1 名单导入与脏数据清理

真实名单永远是乱的,这是年会项目最容易被低估的部分。HR 给的 Excel 里可能有合并单元格、表头说明、备注列、重复人员,甚至有人名前后带空格。

我是这么处理的:

  • 读取 Excel 后先跳过前几行,自动识别包含“姓名”“工号”“部门”的表头行;
  • 将每一行数据格式化成{ id, name, department };
  • 去掉姓名字段首尾空格,过滤空行;
  • 根据工号去重,没有工号时用部门加姓名组合判断。

这里有个细节:**抽奖展示时大屏上只显示姓名,但内部保存一定要带唯一 ID。**因为同一单位很容易有同名同姓的人,如果只按姓名去重,会把人吞掉或者让人重复中奖。用 ID 做防重才是最稳的。

3.2 球体建模与名字排布

球体本身用了一个半径 1 的球体网格,半透明材质。难点在于把几百个名字均匀贴在球面上,并且每个名字都能看清。

我最初试过按经纬度等距排布,结果发现两极地区名字挤成一片,赤道附近反而稀疏。后改用斐波那契球体分布,效果好了很多。斐波那契分布的核心逻辑很简单:

const total = names.length; for (let i = 0; i < total; i++) { const y = 1 - (i / (total - 1)) * 2; const radius = Math.sqrt(1 - y * y); const theta = i * 2.39996; // 黄金角近似值 const x = Math.cos(theta) * radius; const z = Math.sin(theta) * radius; sprite[i].position.set(x, y, z); }

为什么这个分布均匀?因为它按照黄金角在球面上逐点“盘旋”填满整个球面,每个点之间的相对距离大致相等,不会出现纬线上那种越靠近两极越拥挤的情况。这一点在名字数量多的时候尤其关键,我试过 700 个人的名字贴上去,依然能保证每个人名之间有明显间距。

名字本身不直接用 Three.js 的 TextGeometry,那个加载字体太慢、渲染太费。我用 Canvas 预先画一个 128×64 的透明底图,把名字画上去,再做成 Sprite 贴到球面上。Sprite 的好处是始终朝向相机,不用旋转 3D 文本对象,观众从任何角度都能直接看到名字,不会出现名字被球体挡住的情况。

3.3 旋转动画与停止减速

球体旋转分两段:滚动阶段和停止阶段。

滚动阶段就是让整个名字组绕 Y 轴持续旋转,同时保留一个固定倾角,让球体看起来更立体。为了让观众看清名字,旋转速度不能太快,我控制在每秒 0.6 圈左右,大屏上看是“清晰可辨地滚动”,而不是一团模糊。

停止阶段不能像电闸断电一样瞬间定住,那样视觉上太突兀,也不像一个“抽奖仪式”。我用了减速缓动曲线:

function easeOutCubic(t) { return 1 - Math.pow(1 - t, 3); }

停止时记录当前旋转角,在 1.5 秒内让旋转速度从当前值降到零。这个过程观众会感觉球体是“惯性滑落”到一个名字上,而不是被强制掐断。

这里有个视觉设计上的小心机:为了让最终中奖者看起来更“命中注定”,我在点击停止的一瞬间就从剩余名单里选好中奖者,然后计算这个人在球面上的角度,让球体在减速滑行中把该位置带到正前方。观众看到的结果就是:球体慢慢停下来,正中央正好是对准的那个人的名字。整个过程没有任何操作者干预,随机性发生在点击停止的那一刻。

3.4 抽奖公平性与防重逻辑

很多抽奖软件被人吐槽“暗箱操作”,其实问题不在黑幕,而在随机源不够好或者逻辑不透明。我为这个项目刻意保证了两点:

  • 使用密码学安全随机源,而不是普通的伪随机数函数;
  • 每一轮的抽奖池都从“未中奖名单”里取,已中奖者直接从池子里移除。

核心伪代码如下:

function pickWinner() { const pool = unclaimedList; // 未中奖名单 const array = new Uint32Array(1); crypto.getRandomValues(array); return pool[array[0] % pool.length]; }

中奖者确定后,立即从unclaimedList中移除,同时追加到中奖记录里。这个数据更新必须在渲染动画开始前完成,否则容易出现“球停在一个已经中过奖的人身上”的事故。

还有一个容易忽略的点:**连开几个奖项时,很多人想当然地觉得先把名单打乱,然后“从头到尾依次取”就是公平的。**逻辑上同样公平,但大屏效果很差,因为规则可以被观众猜到:第一轮停了第 100 个人,第二轮就是第 200 个人,大家会去数规律,气氛全毁。所以每一轮都重新随机,而且不能让观众看出上一轮的位置和下一轮有任何关联。

3.5 音效、灯光与聚焦

抽奖软件本质是舞台道具,音效占了一半的体验。我用 Web Audio API 做了三段包装:

  • 滚动阶段:轻快的循环背景音乐,音量压低,不抢主持人说话;
  • 减速阶段:音乐渐弱,叠加一声“叮”的提示;
  • 定格阶段:音量拉满一声胜利音响,同时输出“恭喜 xxx”的大字面板。

灯效上,球体周围放了一个光晕粒子环,停止时粒子向中奖者名字位置汇聚,同时把其余名字暂时调暗。这样全场视线会被强制拉到一个点上,效果比单纯文字弹窗好得多。

音效要注意的是预加载:所有音频文件在应用启动时就加载进内存,禁止现场点击时才去读取本地文件。延迟在那时会非常致命。

4. 现场最容易翻车的五个坑与完整排查链路

4.1 旧电脑掉帧:名字一多画面就开始卡

第一次内测时,我把 700 个名字铺到球面上,点击开始轮转,结果画面掉到十几帧。排查链路是这样的:先开性能面板看 GPU 占用,发现纹理上传量很大;再看每张名字贴图尺寸,原来每张 128×64 的透明图在 Canvas 上生成后没做尺寸归一化,字越大纹理越占显存。

结论是降低单位成本:名字贴图统一压缩到 64×32,同时关闭不必要的阴影计算,粒子数量减半,帧率立刻恢复到 40 帧以上。这个经验可以反推一句话:抽奖球上的文字没必要追求高清字体,重要的是清晰和快速。

4.2 接上大屏后字体变成方块

年会现场最常见的显示故障不是卡顿,而是字体丢失。主办方提供的电脑可能没有安装我开发时使用的艺术字体,球面上的人名全部变成方框。

排查后发现,系统自带的默认字体在 LED 大屏上渲染成粗黑体,笔画过粗,名字重叠严重。后来我直接把“思源黑体”的一部分子集打包进应用资源,通过 FontFace 加载,渲染时优先使用。这样不管对方电脑装了什么字体,软件呈现效果都一致。

4.3 双屏模式下鼠标偏移与全屏位置错乱

现场操作间通常会有一个主显示器用于操作者,外接一个大屏用于观众。全屏窗口默认只覆盖主显示器,但我代码里启动时强制覆盖外接屏,问题就来了:物理分辨率不同时,鼠标坐标与实际渲染位置对不上,点击“停止”按钮没反应。

排查原因是我用了系统推荐的“扩展显示器”模式,但没有重新校准设备坐标。解决方案有两个,我同时做了:

  • 应用启动时读取显示器列表,用setWindowBounds精确把窗口设置到外接屏的可用区域;
  • 现场操作方案改为键盘快捷键,传输所有鼠标按钮,彻底避开坐标问题。

这个坑很隐蔽,我建议大家做类似软件时,一定要在“双屏不同分辨率”的组合下完整测一遍,别只在自己一台电脑上自测。

4.4 操作员手滑:一个奖项抽出了两个中奖者

彩排时发生过一次事故:主持人喊停,操作员连按了两次停止键,动画播放完毕后结果显示的是第二个人的名字,而第一位中奖者已经被记入中奖名单,导致名单错位。

问题根源是停止逻辑没有做防抖,两次点击进入了两次独立流程。修复方案是加一个状态机:滚动中 -> 停止中 -> 已结束。只有“滚动中”状态才响应停止键,“停止中”和“已结束”状态直接忽略输入。此外,正式发布版本里我额外加了一个确认弹窗,但现场为了不破坏气氛,弹窗做成了一个半透明浮条,显示“松手确认:再按一次空格键锁定”,操作员按一次触发减速,再按一次才锁死。

4.5 离线环境下的音效加载失败

有一次彩排时我拔掉网线测试,发现背景音乐循环到第二遍就哑了。查下来才知道我写了一个“网络优先,本地兜底”的音效加载逻辑,本地文件读取路径在打包后发生了变化,兜底路径根本不存在。

修复方法是把所有音频文件打进安装包,启动时先直接读本地,任何网络路径都不走。打包后我会专门跑一遍断网测试,确保拔网线后功能完整可用。

5. 打包、压测与活动当晚的操作清单

5.1 打成绿色版单文件

交付现场的不是源码,而是一个免安装 exe。我用打包工具做成 portable 模式,双击即跑,不需要装任何运行库。资源和配置文件放在 exe 同级的 data 目录下,方便现场临时调整音效和名单。

打包时需要注意三点:第一,路径一律用应用运行目录计算,不能写绝对路径;第二,字体、音频、图片全部内嵌到资源目录,防止被系统清理工具误删;第三,关闭无关的系统权限申请,不要在任何杀毒软件弹窗上出现不明签名警告。

5.2 测试矩阵

我在交付前做了一张压测表,要求自己在三种配置上各跑一遍:

测试环境分辨率比例名单容量结果
办公台式机1920×108016:9500流畅,字体正常
老款笔记本核显1366×76816:970040 帧左右,可接受
外接 LED 大屏非标分辨率16:10500拉伸明显,需强制缩放

非标分辨率这一项尤其重要。很多 LED 大屏的“物理分辨率”不是普通显示器规格,如果按窗口默认比例播放,画面会被拉扁。我后来加了一个“保持宽高比并自动黑边填充”的模式,宁可两边留黑边也不拉伸。

5.3 活动当晚的操盘流程

按照真实年会经验,我给自己定了一张流程清单:

  • 提前一小时接好所有线材,设置好外接屏的扩展/复制模式;
  • 打开软件,导入当天最终确认版名单,核对总人数;
  • 全场彩排抽一轮,确认音效从功放到音箱的输出音量;
  • 关闭系统的自动更新、锁屏、屏保,插入电源线;
  • 把中奖名单导出文件路径确认好,方便结束后生成待发奖名单。

5.4 应急兜底方案

再稳的软件都有翻车可能,所以我会同时准备一个普通网页版本的抽奖页面,放在 U 盘里。一旦 3D 版本因为某个环境问题跑不起来,操作员可以秒开备用的“姓名滚动抽奖”页面顶上去。这个页面没有球体,但能保证抽奖仪式不被中断。凡事保留一条退路,是我做现场工具最重要的习惯。

6. 这个 3D 抽奖还能长成什么样

6.1 多轮次与奖项等级

第一版只支持单次抽取一人。后来的版本扩展成了配置式抽奖表单:先定义奖项名称、数量、顺序,比如“五等奖抽 20 人,四等奖抽 10 人……特等奖抽 1 人”。每轮结束,中奖名单从球体上逐渐淡出,剩下的名字继续留在球上,观众能直观看到“池子里的人越来越少”。

这个设计非常有意思,它会制造一种天然稀缺感。尤其抽到最后特等奖时,球面上只剩几十个名字,观众会很激动。

6.2 品牌定制和主题皮肤

我给软件加入了球体纹理替换功能:在球面半透明层上叠加公司 logo 或年会主题色。Logo 不会遮挡名字,只要透明度控制在 30% 左右,既能有归属感又不会影响信息可读性。同样配色方案也可以用于中奖大字面板、按钮状态和粒子颜色。

6.3 设备联动与更多玩法

如果你们年会现场有自己的音控系统,3D 抽奖软件还可以输出触发信号给灯光控制台,让抽奖定格瞬间灯光效果同步。或者做成局域网内一个小程序码,让观众在手机端看到“自己是否仍在奖池中”,而不是依赖主持人宣读名单。

这些内容扩展到春节抽奖、校庆活动、门店促销抽奖也同样适用。核心思路不变:先保证离线、稳定、可配置,再谈效果和互动。

最后分享一个我个人的体会。做这类现场工具,最大的风险从来不是 3D 渲染写不出来,而是你没在“真实现场环境”里跑过。我第一次上线前自我感觉很良好,结果到现场才发现外接大屏的分辨率比例不对,名字全部拉长,手忙脚乱调了一刻钟才恢复。从那以后,我所有作品上线前都强制做一轮“垃圾电脑 + 陌生大屏 + 拔网线”的三连压测。你永远不知道现场会发生什么,能做的就是把所有能控制的变量提前锁死。希望这套 3D 圆球年会抽奖软件的完整拆解,能帮你少踩几个晚上熬夜排障的坑。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/19 20:42:05

基于Agent与Harness的自动化信息挖掘工作流:从采集到归档的工程实践

1. 从“躺平挖 alpha”说起&#xff1a;这套工作流到底在解决什么问题“躺平挖 alpha”这个说法第一次看到的时候我笑了很久&#xff0c;因为它精准地描述了一种状态&#xff1a;人不想卷&#xff0c;但又不想错过机会&#xff0c;于是把重复性的、需要持续盯盘的活儿交给一套自…

作者头像 李华
网站建设 2026/10/17 19:35:43

澄澜V4.1-Flash实测:3倍速度、1/7成本,轻量大模型如何落地生产

澄澜V4.1-Flash发布了&#xff0c;这是我们团队最近实测量最大的一次模型版本迭代。看到"Flash"这个后缀&#xff0c;做过大模型应用的同学应该能猜到七八分——它不追求全能指标&#xff0c;而是把速度、成本和并发稳定性放在第一位。我们连续几天把它接入线上业务做…

作者头像 李华
网站建设 2026/10/17 22:52:29

磁盘分区合并实战:安全扩展C盘、无损合并分区的完整指南

电脑用久了&#xff0c;磁盘分区这个事儿总免不了让人挠头。当年装系统图省事&#xff0c;1TB的盘分了三四个区&#xff0c;结果C盘常年飘红&#xff0c;D盘和E盘反倒闲着大半。想合并吧&#xff0c;又怕动分区丢数据&#xff0c;一直拖到新下的软件连装的地方都没有。这篇文章…

作者头像 李华
网站建设 2026/10/17 23:40:41

汽车嵌入式软件拥抱Rust:沃尔沃应用实践与工程化价值

最近伙伴群里有人转发了一条消息&#xff0c;说是沃尔沃汽车在嵌入式软件方面开始应用 Rust&#xff0c;我当时的第一反应是&#xff1a;汽车行业终于对 Rust 动真格了。说实话&#xff0c;Rust 日报里偶尔能看到一些工业级项目尝试 Rust 的新闻&#xff0c;但大多停留在工具链…

作者头像 李华
网站建设 2026/10/18 6:52:32

华中杯A题解析:绿色物流调度的MIP建模与可运行代码实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/17 21:24:44

儿童近视前兆识别与家庭防控:别忽视眯眼、凑近等信号

1. 为什么很多近视前兆会被家长漏掉&#xff1a;先看清视力发育的底层逻辑先讲个我身边很典型的例子。我朋友家的孩子&#xff0c;小学二年级&#xff0c;平时成绩不错&#xff0c;就是最近总被老师投诉“上课走神”&#xff0c;说孩子看黑板的时候经常愣神&#xff0c;写字速度…

作者头像 李华