获奖站拆解
EN

Bruno's Portfolio

Bruno's Portfolio 是法国创意开发者 Bruno Simon 的个人作品集,整个作品集是一座能开着玩具吉普车四处逛的小岛。屏幕中起始是一道发光的圆环,圆环收成一点后,一小块圆形地面从中间长出来,上面停着一辆红色吉普车。访客点一下开始,这块地面就扩成整座岛,车开到哪里,访客就看到哪一部分内容。岛上的天色和季节跟着真实的时钟变化,同一时刻打开网站的访客看到同一个黄昏,别的访客留下的话也飘在岛上。

Bruno Simon 一个人设计和开发了这个站,FWA 把 2025 年的年度最佳和访客投票的年度人气奖都给了它,两个平台的当日最佳和月度最佳它也都拿到了,Awwwards 另外给了它开发奖。

白天的落地区,地上立着淡紫色的立体大字 BRUNO SIMON,红色吉普车停在一盏路灯和一棵粉红色的树旁边,车旁飘着别的访客留下的一句 LOVING YOUR WORK BRUNO!,画面上方中间的黑色通知条写着 SERVER CONNECTED,右边是一间红顶小屋
白天的落地区,地上立着淡紫色的立体大字 BRUNO SIMON,红色吉普车停在一盏路灯和一棵粉红色的树旁边,车旁飘着别的访客留下的一句 LOVING YOUR WORK BRUNO!,画面上方中间的黑色通知条写着 SERVER CONNECTED,右边是一间红顶小屋

01概念

Bruno's Portfolio 的菜单首页写着一段欢迎词,Bruno Simon 在欢迎词里说自己是一名主要做网页的创意开发者,请访客开着车四处转转,了解作者是谁,再去发现这个世界里的许多秘密,最后还叮嘱访客不要把东西打坏。作品集通常是一页项目列表,而这个站把项目、经历和联系方式拆进了一座岛上的十三个区域,访客想看什么,就要把车开过去。这样一来,找内容本身成了一件好玩的事,但访客也要先学会开车,才看得到具体的项目。

岛上的区域分成两类,一类放作品集的正经内容,项目区把作品印在立在地上的展板上,讲工作经历的区域把每一段经历做成地上一条不同颜色的长条和一块刻着文字的石板,另有一个实验室。岛上另有一个区域摆着一台时光机,访客把车开过去按下互动键,网站就打开 Bruno Simon 2019 年的上一版作品集。另一类区域只是为了好玩,比如一条绕着半座岛跑的赛车道、一座霓虹保龄球馆和一间晚上门上亮着月亮的木头厕所。访客从岛中间的落地区出发,到哪一类区域都不远。访客按 M 键还能打开整座岛的俯视地图,地图上的白色菱形标着各个地点,点一下其中一个,吉普车就直接传送过去。

整座岛的俯视地图,左边和上方是一条红白路肩的赛车道,中间和右边是草地、沙地和蜿蜒的水道,十几个白色菱形标着各个地点,右下角是红色吉普车的图标,地图两边露出后面被压暗的落地区
整座岛的俯视地图,左边和上方是一条红白路肩的赛车道,中间和右边是草地、沙地和蜿蜒的水道,十几个白色菱形标着各个地点,右下角是红色吉普车的图标,地图两边露出后面被压暗的落地区

Bruno's Portfolio 还给访客准备了许多留下来、再回来的理由。菜单里的成就页列着 38 个成就,比如走遍所有区域、看完项目区里的全部项目,还有一条是在网站上连续待满一整个昼夜,Bruno Simon 在 Awwwards 的制作说明里写到,解锁成就还能换吉普车的外观。赛车道有一张每天重置的排行榜,列着前十名访客的国旗和圈速。访客按 T 键能留下一句不超过 30 个字符的悄悄话,岛上最多同时留着 30 句,每位访客只能留一句,新的会挤掉最旧的。岛上的饼干摊和一座祭坛各有一个计数器,所有访客在那里的互动都累加进同一个数。

这些玩法之后,作品集的终点是菜单里的幕后介绍页。这一页讲这个站用了哪些技术,感谢 three.js 的作者和给 three.js 加上 TSL 的开发者,再把访客引到 Bruno Simon 自己的 three.js 课程 Three.js Journey、制作过程的视频日志和 GitHub 上公开的源代码。公开的源代码用 MIT 许可,连 Blender 文件都在里面。一个访客看完整座岛、好奇它怎么做出来时,网站最后把这位访客带到的正是教人做这类网站的课程,所以这座岛同时也是这门课最好的样品。

02设计

Bruno's Portfolio 把整座岛做成一个摆在桌上的玩具沙盘。镜头始终从同一个角度斜着往下看,访客只能平移和缩放,不能转动镜头,所以岛上的一切都像一组缩小的模型。模型全部是低多边形,由大块的平面拼成,没有写实的质感。草是一片片尖尖的三角形,树冠是一团团碎块,水边画着白色的浪线。Bruno Simon 在制作说明里写到,整座岛的颜色都取自同一张小小的色板,每个模型只是在色板上选格子,所以红、橙、粉、紫这几种颜色在整座岛上反复出现,画面再热闹也不会乱。

岛上的光和颜色由天色决定,这是画面上变化最大的地方。网站给一天里的四个时段各定了一套颜色,每套颜色包括阳光、阴影和雾的两种颜色。白天的阳光是暖桃色(#FFD2C2),阴影却是饱和的紫色(#6D3FFF),雾从青色过渡到淡紫。黄昏的阳光变成珊瑚红(#FF8181),雾从蓝色过渡到粉红,黎明的阳光是橙色(#FFA882),阴影偏洋红(#DB004F)。夜里的光是深蓝色(#3240FF),亮度是白天的三倍多,整座岛沉进一片蓝紫,灯笼和招牌的暖光成了最显眼的颜色。四套颜色里的阴影都用一种比阳光更冷、更饱和的颜色,没有一套用灰色,所以阴影里的东西不会变脏,画面一直像一张卡通插画。

夜里的一处岸边,整片画面是深蓝紫色,水面边缘泛着亮蓝色的浪线,水上漂着许多落叶,一间门上亮着黄色月亮的木头厕所旁边点着一盏灯笼,红色吉普车停在沙地上,远处还有两盏灯笼
夜里的一处岸边,整片画面是深蓝紫色,水面边缘泛着亮蓝色的浪线,水上漂着许多落叶,一间门上亮着黄色月亮的木头厕所旁边点着一盏灯笼,红色吉普车停在沙地上,远处还有两盏灯笼

岛上的植物用很少的形状做出了很多层次。每一棵树是一根树干加上一团朝着镜头的叶片,叶片上画着一片片叶子的轮廓,迎着阳光的一面和背光的一面用两种颜色。岛上有白桦、橡树和樱花三种树,樱花是粉红色的,另外两种树的叶子在画面上是黄、橙和红色。树冠挡在吉普车和镜头之间时,挡住车的那一块叶子会变得稀疏透明,访客始终看得见自己的车。

Bruno's Portfolio 的界面只有几样东西,配色和字体却定得很清楚。标题、通知和岛上飘着的文字都用一款细长的手写体 Amatic SC 的粗体,比如 CLICK TO START、SERVER CONNECTED 和访客的悄悄话,正文用圆润的 Nunito。面板是两种很深的紫灰色(#251F2B、#1D1721),文字是白色。强调色是一种很淡的粉色(#FFCECA),用在菜单的图标、成就的标题和进度条,以及通知下方的倒计时细条上。成就达成、操作成功的地方换成嫩绿色(#D5FF95),菜单、地图和关闭这几个按钮是深红色(#C21515)。

网站界面部件的形状和配色一样简单,样式里没有一处圆角,所有面板和按钮都是直角,和手写体放在一起像是用纸片和马克笔做出来的。访客打开菜单后,上方一排七个图标格子切换首页、设置、成就这些页面,选中的那一格比其余几格亮,每一页的左边都配着一块实时渲染的岛上场景。

到了手机和平板上,岛还是同一座岛,镜头的角度更陡一些,更接近从正上方往下看,界面则按窄屏重新排过。开始画面上的 CLICK TO START 换成 TAP TO START,右上角的菜单和地图按钮叠成一列方块。车开到可以互动的地方时,屏幕下方会出现一个写着 INTERACT 的大按钮,代替桌面上的回车键。菜单在手机上铺满整个屏幕,那块实时渲染的场景从左边挪到了文字上方。

手机上的三个画面,左边是开始后的落地区,中间是手指按住屏幕时吉普车周围出现的白色圆环,车旁标着 MAP,屏幕下方写着 INTERACT,右边是铺满屏幕的菜单,上方一排图标,图标下面是一块吉普车停在圆形地面上的画面和 BRUNO'S HOME 的欢迎词
手机上的三个画面,左边是开始后的落地区,中间是手指按住屏幕时吉普车周围出现的白色圆环,车旁标着 MAP,屏幕下方写着 INTERACT,右边是铺满屏幕的菜单,上方一排图标,图标下面是一块吉普车停在圆形地面上的画面和 BRUNO'S HOME 的欢迎词

03动效

Bruno's Portfolio 的开场把加载过程本身做成了动画。黑色的地面上铺着一层紫色的十字网格,网格中间那道发光的圆弧就是加载进度,资源下载得越多,圆弧绕得越长。圆弧不会一下跳到新的位置,每一帧只追上剩下距离的一小部分,所以绕得很顺。下载完成后,合拢的圆环先慢后快地收成一个点,用时约 1.5 秒。

圆环收没之后,一块圆形的地面从同一个点向外长开,约 2 秒长到最大,长到头时还会稍微冲过一点再退回来。与此同时,镜头在约 1.25 秒内从很近的位置往后退了一段。地面长开 1 秒后,手写的 CLICK TO START 带着一个喇叭图标弹出来,弹的时候像果冻一样晃几下才停稳。光标移到这块地面上时,地面的亮度在 0.2 秒内提高两成左右,告诉访客这里可以点。

访客点击开始后,那块圆形地面接着往外扩,这一次一直扩到整座岛,CLICK TO START 先在 0.3 秒内缩没。地面先往回缩一点,再越来越快地向外扩开,约 2 秒后整座岛都露了出来,镜头同时在约 1.75 秒内继续往后退,退到开车时的正常距离。地面的边缘一直是一圈发光的粉紫色细线,所以访客看到的是一个光圈向外推开,把整座岛一点点亮出来。

开场之后,访客的主要动作就是开车。吉普车起步很快,约 0.6 秒就到了最高速,访客松开按键后约 1 秒停稳,所以车开起来轻快、听手,又不会一松手就停死。车有四个各自起伏的悬挂,开过颠簸的地方车身会晃,开得太猛还会翻过来。浏览器标签页上的标题也跟着车速变化,里面的吉普车表情符号开得越快,后面的树退得越快。

镜头跟着车走,但和车之间留着一点弹性。镜头每一帧只追上剩下距离的一小部分,所以车总是稍稍领先镜头,转弯和加速时画面带着一点拖拽感。桌面上车速越快,镜头拉得越远,访客开快车时能看到更大的一片岛。访客按住左键拖动时,镜头离开车子往拖的方向平移。镜头像被一根橡皮筋拴在车上,拖得越远,往回拉的力越大,访客松手后,镜头自己回到车上。

岛上的东西大多可以撞,长椅、灯笼、砖块和立体大字都会被车撞飞,岛上还散着几个红色的炸药箱。车碰到炸药箱时先响一声咔嗒,约 0.4 秒后箱子爆开,一团卡通火球在 0.6 秒内先快后慢地胀到最大,再在约 2 秒里从里往外烧穿,烧穿的边缘带着一圈深色的轮廓。爆炸把周围约 5 米以内的东西往上、往外掀开,离得越近掀得越猛,镜头也跟着歪一下,离爆炸越近歪得越厉害,地上的落叶同时被吹散。爆炸离车很近时,整个游戏会放慢到半速约 1.5 秒,放慢只用三分之一秒就到位,恢复却要约 3 秒,像电影里的慢镜头。

炸药箱爆开的一刻,画面中上方是一团黄红色的卡通火球,BRUNO SIMON 后面的 O、N 几个字母被炸飞到半空,M 倒在一座插着红色长剑的骷髅祭坛旁边,左边是一张长椅和几棵粉红、黄色的树,右上角是一个由键盘、手柄和摇杆组成的雕塑,右下角水边堆着一堆白色砖块和一盏灯笼
炸药箱爆开的一刻,画面中上方是一团黄红色的卡通火球,BRUNO SIMON 后面的 O、N 几个字母被炸飞到半空,M 倒在一座插着红色长剑的骷髅祭坛旁边,左边是一张长椅和几棵粉红、黄色的树,右上角是一个由键盘、手柄和摇杆组成的雕塑,右下角水边堆着一堆白色砖块和一盏灯笼

岛上就算在访客停下车时也一直在动,树叶和草随着风摆动,风越大摆得越厉害。落叶在地上打转,车开过时把它们卷起来、推开。一个昼夜是 4 分钟,白天和夜里各占约一分钟,中间隔着黄昏和黎明。各段之间的光和颜色是慢慢过渡的,所以访客开着车,四周就从白天变成黄昏,再变成夜里。季节跟着现实的日历走,秋天岛上落叶最多,冬天气温够低时会下雪、水面结冰,湿度和云量够高时还会下雨、打闪电。

到了手机上,开车换成了一种只要一根手指的方式。访客按住屏幕时,车周围的地上出现一个白色的圆环。手指在圆环外的哪个方向,车就往哪个方向开,手指离车越远油门越大,圆环里的填充也跟着变长。访客的手指按在车的后方时,车会倒退,两根手指拖动和捏合则用来平移和缩放镜头。

Bruno's Portfolio 的岛上一直有车、草、树和天气在动,吉普车停着不动和加速穿过整座岛时,画面都保持在每秒约 60 帧。访客在系统里打开减少动态效果的设置之后,网站不读这个设置,落叶、风、天色和开场动画都照常播放。

04工程

Bruno's Portfolio 用 three.js r183 的 WebGPURenderer 绘制整座岛,所有着色器都用 three.js 的节点语言 TSL 写成,同一份代码在支持 WebGPU 的浏览器上编译成 WGSL,在不支持的浏览器上退回 WebGL,菜单的设置页里也显示着当前用的是哪一种。

整个游戏只有一个循环,由渲染器的 setAnimationLoop 驱动,循环里的每个系统按固定的编号排队执行。循环先读输入,再让玩家和车在物理之前施力,然后 Rapier 物理引擎走一步。物理之后才轮到镜头、昼夜和天气、岛上的各个系统,然后更新实例化的模型、声音和界面,最后才渲染。镜头因此总是读到这一帧物理算完之后的车的位置,画面不会抖。

整座岛是在 Blender 里搭出来的,Blender 文件同时也是关卡编辑器。Bruno Simon 在制作说明和 GitHub 的说明文件里写到,模型的名字决定它在游戏里的行为,名字里带着 Physical 或 Fixed 的物体会变成可以撞动或者固定不动的物理物体,碰撞体、重生点和区域的范围都用 Blender 里的空物体标出来。所有模型的颜色来自同一张色板贴图,每个面的 UV 指向色板上的一个格子,导出时把 Blender 里预览用的色板节点关掉,网站再统一换上自己的材质。模型用 Draco 压缩并量化顶点,贴图用 gltf-transform 和 toktx 压成 KTX,大部分用 ETC1S,少数需要细节的用 UASTC。访客的浏览器用 Basis 转码器把这些 KTX 贴图转成显卡直接能用的格式,在桌面上就是 BC7。

吉普车用的是 Rapier 的车辆控制器,车身是一个动态刚体,四个轮子是向下打的射线,各自带着悬挂的弹簧和阻尼,所以车开过不平的地面时会起伏。网站另外检查车是不是卡住、翻倒或者四轮朝天,访客按 R 键或者在设置里按 I'm stuck 就能回到重生点。岛上的炸药箱是一开始处于休眠的轻质刚体,碰撞回调触发爆炸。爆炸的冲量按距离线性衰减,方向是向外再加一个向上的分量。爆炸太靠近车时,网站同时把游戏循环和 GSAP 全局时间线的时间比例调到 0.5,物理、粒子和所有补间动画因此一起放慢,慢镜头不需要为每个系统单独处理。

岛上那片草地用一次绘制画完,约 7.84 万根草,每根草只是一个三角形,全部装在同一个几何体里,排成 280 乘 280 的方阵。方阵并不铺满整座岛,而是围着镜头的焦点。着色器把每根草的位置按方阵的大小取模,所以车往前开时,落到身后的草会绕到前面去,看起来像一整片无边的草地。每根草的高度带一点随机,颜色从地形贴图上取,所以草和下面的地面颜色一致。

同样的省法也用在岛上其他大片或重复的东西上。水面、地形和雨也用同样的循环方式,只围着镜头画一块。树、叶片、长椅、灯笼和砖块这些重复的东西都用实例化绘制,整个场景里 24 组实例化模型一共画出约 2,950 个物体,再加上视锥剔除,每帧只有约 250 到 275 次绘制。

树冠和落叶各有一段专门的着色器,让它们跟着风和车动起来。每一团叶片都是朝着镜头的平面,片元着色器用一张叶子形状的贴图按 0.3 的阈值抠出轮廓,贴图的取样位置跟着风旋转,叶子的轮廓就会在风里抖动。着色器还知道吉普车在屏幕上的位置,着色器把离车越近的叶片抠掉得越多,这就是车被挡住时树冠变透明的原因。地上的落叶是 GPU 上的计算粒子,数量按季节在 128 到 2048 片之间变化。每一帧,网站把车的位置和速度传给计算着色器,由它把落叶推开、卷起,爆炸也从这里给落叶加一个冲量,这就是每一帧里那一趟计算通道。

昼夜和季节不需要服务器,却对所有访客同步。昼夜循环和季节循环都直接用 Date.now() 除以周期的长度算出进度,昼夜的周期是 240 秒,季节的周期是真实的 365 天,所以世界各地的访客在同一时刻算出来的是同一个时辰和同一个季节。每个时段和季节是一组关键帧,进度落在两组之间时,颜色和数值按比例插值。

天气在季节的基础上再加一层变化,而且同样对所有访客同步,因为天气用的噪声也以真实时钟算出的昼夜进度为输入。气温等于季节的气温加上昼夜的起伏,再加一段由三个不同频率的正弦相乘得到的噪声,所以气温会不规则地上下浮动。湿度在 0.65 以上开始下雨,雨量再乘以云量,气温降到 0 度以下时,雨就变成雪。

Bruno's Portfolio 真正连服务器的只有悄悄话、赛车排行和两个全局计数器,它们走同一个 WebSocket 连接。服务器离线时,页面会带上离线的标记,岛照常能玩,只是赛车的成绩存不下来。制作说明里写到,悄悄话在显示之前会先过一遍 OpenAI 的内容审核。

整座岛的每一帧都要先画进几张缓冲,再做几道后期处理,一帧大约要画 19 趟。阴影贴图是 2048 乘 2048,场景先画进四张全分辨率的半精度浮点缓冲。泛光在半分辨率上做,阈值设成 1,只有比白色还亮的灯笼、火球和开场的光圈才会发光。高画质下,泛光之前还有一道便宜的景深,把画面边缘轻轻模糊。像素比低于 2 的屏幕上,网站另外打开多重采样抗锯齿。

开场的圆形揭示也是这套材质的一部分,所有材质共用一个半径参数,离起点的水平距离超过这个半径的像素直接丢掉,边缘一圈很窄的带子画成乘了 5.5 倍亮度的粉紫色,所以泛光会把它变成一道光圈。黑色网格的地面正好反过来,只在半径以外画,开场时网格和岛就在这条边上交接。

网站按浏览器的 UA 判断设备,手机和平板一律换成低画质。低画质的泛光只做两级模糊,桌面是五级,景深和随车速拉远的镜头都关掉,阴影贴图从 2048 降到 512,物理改用固定的六十分之一秒步长。手机上的画布另外把像素比限制在 2 以内,像素比为 3 的手机只画九分之四的像素。设置页里有一个 High 和 Low 的开关,访客可以自己换。所有设备下载的资源却完全一样,手机拿到的模型和贴图和桌面相同,只是画得少一些。

这些资源加起来约 7.56 MB,最大的一个文件是约 1.66 MB 的 Rapier 物理引擎,它编译成 WebAssembly 运行,其次是约 1 MB 的主脚本,再往下是约 625 KB 的区域模型和约 515 KB 的 Basis 转码器。42 段 mp3 声音合计约 1.93 MB,是体积最大的一类资源。服务器在约 0.12 秒内开始返回页面,加载的圆弧在 3.0 到 3.3 秒之间第一次画出来,那块圆形地面和 CLICK TO START 大约到第 8.5 到 9 秒才完整出现。加载期间页面最长卡住约 0.85 秒,画面不跳动。

声音由 Howler 播放,访客点击开始之后才响。背景音乐是音乐人 Kounine 专门为这个站写的三首曲子,现在用 CC0 许可公开。岛上的声音大多带着位置,比如炸药箱的爆炸声,离车越远越小,超过一定距离就听不见。同一种声音每次播放时音高都随机变一点,爆炸声在原速的 0.9 到 1.2 倍之间,所以接连撞几次也不会听到一模一样的声音。

Bruno's Portfolio 可以完全用键盘玩,也支持手柄和触屏,但读屏软件的用户几乎用不了它。网站拦下了 Tab 键,焦点一直停在右上角的菜单按钮上,菜单和地图两个按钮都没有名字,读屏软件只知道那里是一个按钮。岛上的内容全部画在画布里,读屏软件读不到任何一个项目。

05评分与团队

奖项 出处 日期 分数
FWA of the Day The FWA 2025-12-13 93 POINTS,DAYS 2
FWA of the Month The FWA 2025 年 12 月 月度最佳,不另打分
FWA of the Year The FWA 2025 年 年度最佳,不另打分
People's Choice Award The FWA 2025 年 访客投票的年度人气奖
Site of the Day Awwwards 2026-01-21 8.11。Design 8.05 / Usability 7.83 / Creativity 8.62 / Content 8.18
Developer Award Awwwards 2026-01-21 7.65
Site of the Month Awwwards 2026 年 1 月 月度最佳,不另打分

FWA 把 2025 年的年度最佳评给了 Bruno's Portfolio,同一年访客投票的人气奖也是它,它的当日最佳得了 93 分,归入趣味可玩、游戏和个人与作品集三个类别。Bruno's Portfolio 在 Awwwards 拿到的是 2026 年 1 月的当日最佳、开发奖和月度最佳。

Bruno's Portfolio 在 Awwwards 的四项里,创意 Creativity 得分最高,为 8.62 分,把作品集做成一座能开车、能比赛、能留言的岛,很可能是这一项高的原因。可用性 Usability 最低,为 7.83 分,访客要先学会开车、找到地方,才看得到具体的项目,这很可能拉低了这一项。开发奖的五位评委给出 7.65 分,动画 Animations 最高,为 8.60 分。无障碍 Accessibility 最低,为 6.60 分,和工程章里被拦下的 Tab 键、没有名字的按钮正好对应。

署名 身份 战绩
Bruno Simon 法国巴黎的创意开发者,自由职业,曾任 Immersive Garden 的首席开发 Awwwards 作品 10,SOTY 1,SOTM 2,SOTD 7,HM 10。FWA 16

这个站的建模、关卡和代码都出自 Bruno Simon 一人,背景音乐请了 Kounine 来写。Bruno Simon 在制作说明里写到,这一版花了一年多,2025 年底上线,它的上一版是 2019 年那个同样能开车的作品集,现在就藏在岛上的时光机里。Bruno Simon 截至写报告时在 FWA 上一共拿过 16 个奖,其中当日最佳、月度最佳、年度最佳和人气奖这四个来自这个站。

06档案

获奖日期
2026-01-21
奖项
Awwwards SOTD 2026-01-21,Awwwards DEV 2026-01-21,Awwwards SOTM 2026-01,The FWA FOTD 2025-12-13,The FWA FOTM 2025-12,The FWA FOTY 2025
站点
https://bruno-simon.com/
署名
Bruno Simon
设计取向
spatial
技术路线
realtime
Awwwards
8.11 / 10。Design 8.05 / Usability 7.83 / Creativity 8.62 / Content 8.18。开发奖 7.65。https://www.awwwards.com/sites/brunos-portfolio
The FWA
93 POINTS,DAYS 2。https://thefwa.com/cases/brunos-portfolio
技术栈
three.js r183 WebGPURenderer,TSL,Rapier,GSAP,Howler.js,Draco,KTX(Basis),WebSocket
资产总计
约 7.56 MB,其中声音约 1.93 MB,Rapier 物理引擎约 1.66 MB
首屏与帧率
FCP 2996 到 3344 ms,开始画面约 8.5 到 9 秒完整出现,CLS 0。60 fps,含开车加速