> 这是 https://www.imranjan.cn/zh/projects/p-portfolio 的 Markdown 版本，供 AI 助手和其他程序读取，内容与网页上的公开信息一致。生成于 2026-10-31。

2025年7月至今，持续维护

# imranjan.cn

## 带 3D 景观和 AI 对话的个人网站

我从 2025 年 7 月开始设计、开发并维护这个网站，用来讲清楚简历里放不下的项目细节。访客可以走进一片 3D 景观，也可以和网站上的 AI 版我对话，它会查我公开仓库里的代码来回答。

标签: 全栈开发, WebGL 3D, 性能优化, AI 智能体

![imranjan.cn](https://www.imranjan.cn/images/projects/portfolio-2025/cover.jpg)

## 为什么做这个网站

2025 年 7 月准备留学申请时，我发现简历只有一页，文书也有字数限制，每个项目只能写一两行：用了什么技术，拿了什么奖。为什么这样设计，中途换过几次方案，哪里没做好，这些都没有地方写。

于是我做了这个网站。每个项目一页，讲清楚背景、我负责的部分、怎么实现和结果；也放简历里没有位置的东西，比如我的摄影作品，以及我对交互的一些想法。2025 年 8 月 10 日第一版上线，之后一直在改。

网站从设计、3D 场景搭建、前后端开发、网站 AI 到部署，都是按我自己的想法，和 Claude、Codex 一起完成的。现在首页有两个入口：走进 3D 景观，或者直接和网站上的 AI 版我对话；手机访问时直接进入对话首页。

![首页的入口选择：左边是 3D 景观，右边是和我对话](https://www.imranjan.cn/images/projects/portfolio-2025/chooser.jpg)

首页先让访客选择入口，选择之前不加载任何 3D 代码和资源

## 整体结构

内容和页面是分开的：项目、照片、个人信息和技能都写在数据文件里，中英文成对保存，页面只负责排版。新增一个项目只要在目录里加一条，它的页面路由、预渲染和站点地图都会跟上。

构建在 Vercel 上完成：Vite 按页面拆分代码，Three.js 单独打包；Nitro 把内容页预渲染成静态 HTML，并给每页生成一份 Markdown。

访问时，页面和资源直接从 Vercel 的边缘网络返回，图片由 Vercel 的图片服务按需转成 WebP 并缓存。只有对话、访客数和点赞会调用新加坡的服务端函数，再由函数访问 Redis、Postgres 和两个 AI 接口。

![网站整体结构图：GitHub 仓库、Vercel 构建、访客浏览器、Vercel 边缘网络与函数，以及外部服务](https://www.imranjan.cn/images/projects/portfolio-2025/site-architecture.png)

网站整体结构：上面是构建和发布，下面是一次访问

### 技术栈

#### 前端

Nuxt 4Vue 3Three.jsCanvas 2Dvue-i18n

#### 3D 制作

BlenderglTFDracoWebP

#### AI 与检索

DeepSeekCloudflare Workers AISupabase Postgrespgvector

#### 服务与部署

VercelNitroUpstash RedisGitHub

## 性能与加载

网站里最重的是 3D 场景和照片，加载策略围绕一点：访客没选的不下载，没打开的不加载。

约 110 KB首页的 JavaScript（brotli 压缩后）

约 190 KBThree.js 和场景代码，选择 3D 后才下载

约 10 倍页面图片转成 WebP 后变小的倍数

92%摄影作品压缩掉的体积（366.7 MB 到 27.7 MB）

##### 按入口加载

首页先让访客选择入口，3D 和对话两个首页都是按需加载的组件，选择之前不下载任何 3D 代码；手机直接进入对话首页。

##### 图片

全站图片经过 @nuxt/image 交给 Vercel 的图片服务，按需输出 WebP；照片在清单里记下宽高，加载时版面不跳动；项目页的大图点开时才加载原图。

##### 缓存

3D 资源的文件名带内容哈希，可以缓存一年；资源清单每次都重新验证，发布新场景时访客不会拿到新旧混搭的文件。

##### 摄影页

只挂载当前屏幕宽度对应的那种布局。横向画廊跟随滚动，只在移动时逐帧计算，标签页隐藏时停下。

![摄影页的横向画廊：中间的照片最大最清楚，两边的照片缩小变淡](https://www.imranjan.cn/images/projects/portfolio-2025/gallery.jpg)

摄影页的横向画廊，离中心越远的照片越小越淡

## 双语与搜索

英文是默认语言，中文在 /zh 下，每段文字都中英成对维护。每个页面的标题、描述、规范网址、分享图和结构化数据都从同一份配置生成，并通过 hreflang 告诉搜索引擎另一种语言的页面在哪里。3D 首页在服务器渲染的 HTML 里也有可读的标题和导航，没有 WebGL 也能被搜索引擎读到。

越来越多人会让自己的 AI 助手来读网页，所以每个页面在构建时还会生成一份 Markdown，内容和网页一致：请求头带 Accept: text/markdown 时，同一个网址直接返回 Markdown；页头的 MD 按钮可以手动打开。网站 AI 读的正文也是这份 Markdown。

```
curl -H "Accept: text/markdown" https://www.imranjan.cn/zh/projects/p-portfolio
```

用命令行请求本页的 Markdown 版本

## 3D 景观

我很早就想把脑海里的画面做成一片能走进去的 3D 景观。2026 年 9 月看到 GPT-6 Astra 宣传片的那一刻，我就开始动手：在 Codex 里用 Blender 官方 MCP 和 computer use，按我的想法搭出了整个场景，建筑细节、天气效果和性能优化再用 Fable 5.1 精修。

选 3D 入口后，首页是一片连续的景观。四个地方连成一条路：有风沙的沙漠、下雪的城市、下着小雨的校园和晴天的海边城市。可以滚动鼠标沿路导览，也可以打开自由漫游，用 WASD 和方向键走动、拖动视角。路边立着我的照片，点任意一张，镜头会平稳推到它正前方。

两处校园参考照片重新建模：电子科技大学的品学楼庭院，校名石上的字按照片描出轮廓；新加坡国立大学的 UTown，按照片压缩重组。山体用 SRTM 高程数据重新采样后缩放摆放，部分树木、岩石和材质用了 Poly Haven 的 CC0 素材。

[场景素材来源与许可](https://www.imranjan.cn/ecology/CREDITS.md)

![3D 景观的起点：三块画板立在湖边，远处是雪山](https://www.imranjan.cn/images/projects/portfolio-2025/start.jpg)

起点的三块画板，左边的画板就是对话入口

5 + 8个模型分组，5 组常驻，8 组按位置加载

22.2 MB场景资源，照片另外按需加载

12 秒离开一个区域后释放它的细节

30 次/秒移动时阴影和水面反射的更新上限

![雪地上的导览路线，两边立着照片](https://www.imranjan.cn/images/projects/portfolio-2025/walk.jpg)

沿路立着照片，可以滚动前行，也可以自由漫游

![镜头推到一张晚霞照片的正前方](https://www.imranjan.cn/images/projects/portfolio-2025/focus.jpg)

点一张照片，镜头平稳推到它正前方

### 从 Blender 到浏览器

改完场景后，一条命令完成导出、压缩、按区域拆分、校验和安装；任何一步失败，线上都保持原来的版本。浏览器按镜头位置决定加载哪些区域的细节，把贴图上传和预热分散到多帧完成，走动时不会因为一次准备太多而卡住。

![3D 场景的结构图：上面是构建和发布的五个步骤，下面是浏览器里的流式加载、分帧准备和渲染](https://www.imranjan.cn/images/projects/portfolio-2025/landscape-pipeline.png)

3D 场景：(a) 构建和发布，(b) 浏览器里的运行

### 天气和声音

每个地方有自己的天气：随机风沙、降雪和小雨，海边保持晴天。每种天气的粒子是一个实例化批次，在 GPU 上动画，离开后释放；风沙和雨有环境音，进入场景时背景音乐自动降低音量。

![雨中的校园：品学楼庭院、校名石和喷泉](https://www.imranjan.cn/images/projects/portfolio-2025/rain.jpg)

下着小雨的校园，按照片重新建模的品学楼庭院

![新加坡国立大学 UTown 的弧形建筑和两栋宿舍楼](https://www.imranjan.cn/images/projects/portfolio-2025/campus.jpg)

晴天的 UTown，按照片压缩重组的建筑群

## 和我对话

2D 首页是一个对话首页：中间是可以用鼠标拨动的粒子人像，下面是输入框和推荐问题。访客可以问我的经历和项目，问某个项目的代码怎么写，也可以像平常一样随便聊。它用第一人称“我”回答，但始终标明是 AI，也不会替我答应任何事。

3D 首页起点左边的“Allio!”画板是同一个入口：镜头停稳后，用相机投影算出画板四个角在屏幕上的位置，把对话框贴上去。2D 和 3D 共用同一段对话。

![对话首页：粒子人像下面是输入框和推荐问题](https://www.imranjan.cn/images/projects/portfolio-2025/chat-home.jpg)

对话首页，人像由粒子组成，可以用鼠标拨开

![3D 起点的画板上贴着对话框，正在回答问题](https://www.imranjan.cn/images/projects/portfolio-2025/board-chat.jpg)

在 3D 里点击画板，对话框贴在画板上

### 一次提问经过哪些步骤

推荐问题直接用缓存的回答；其他问题先过限流和问题检查，再交给模型，模型最多调用 4 轮工具，最后再检查一遍才边生成边发给浏览器。网站正文整份放在提示里，能用上前缀缓存；代码则通过混合检索查找。

![网站 AI 的结构图：回答一个问题的流程、知识的准备，以及查代码时的混合检索](https://www.imranjan.cn/images/projects/portfolio-2025/chat-architecture.png)

网站 AI：(a) 回答一个问题，(b) 准备知识，(c) 查代码时的混合检索

![一次回答：文字、项目卡片和展开的回答过程](https://www.imranjan.cn/images/projects/portfolio-2025/chat-answer.jpg)

每条回答都能展开，看调用了哪些工具、多少 token、花了多少钱

![代码问题的回答，引用的代码链接都固定到具体提交](https://www.imranjan.cn/images/projects/portfolio-2025/chat-code.jpg)

回答代码问题时，引用的链接固定到具体提交

### 检索评测

我准备了 32 道题，中英各半，每题标好答案在哪个文件。等权混合反而不如只用向量，因为关键词会把 README 这类处处都沾一点的文件顶上来，所以最后只在问题里出现代码名称时才加上关键词。查询前加一句 Qwen3 要求的任务说明，只用向量时的前五命中率从 84% 升到 97%；切块时在每块代码前写上项目名和文件说明，首位命中从 63% 升到 69%。

__代码检索评测，32 道题__
| 做法       | 前五命中 | 首位命中 | MRR  |
| -------- | ---- | ---- | ---- |
| 只用关键词    | 56%  | 41%  | 0.47 |
| 只用向量     | 97%  | 69%  | 0.80 |
| 等权混合     | 88%  | 53%  | 0.68 |
| 按需混合（采用） | 97%  | 63%  | 0.76 |

回答时模型会读排在前 6 的代码，所以我更看重前五命中。直接写出函数名或文件名的 11 道题，按需混合前五全部命中，只用向量是 91%，所以保留了关键词。MRR 是第一个正确文件排名倒数的平均值，越接近 1 越好。

照片搜索用的是同一个向量模型：18 道有对应照片的题，首位命中从关键词匹配时的 72% 升到 89%，返回的照片里属于正确答案的比例从 45% 升到 89%。

回答里涉及个人隐私的内容会被直接过滤；代码只检索公开仓库，切块时还会替换掉疑似密钥的内容。每次改规则或换模型，我都会用一套事实题、代码题和攻击题重新评测。

## 技术难点

##### 切回英文又被带回中文

**问题**英文页面在根路径 / 下，中文在 /zh 下。如果每次访问 / 都按记住的语言跳转，访客从中文点回英文时也会访问 /，马上又被带回中文。

**做法**记住的语言只在首次进入网站时生效，之后每次明确的跳转和语言切换都会更新记住的选择。

##### 关着的弹窗也在下载原图

**问题**项目页的大图放在弹窗里，弹窗关着时浏览器照样会下载里面的原图，BYOF 一页刚打开就多下载了 8 张原图，共 2.55 MB。

**做法**原图只在第一次打开弹窗时才挂载，正文里的预览图仍按需懒加载。

##### 走回起点时画面停顿

**问题**回到起点时，三块欢迎画板要重新生成文字纹理。画板的大 Canvas 在 GPU 上，读回它会和正在渲染的场景同步，画面卡一下。

**做法**画板改用 CPU 绘制，先生成图片快照，再在单独的一帧里上传成纹理。

##### 访客数一直不出来

**问题**访客数等浏览器空闲时才去请求，而粒子动画让主线程一直不空闲，空闲回调永远不触发。

**做法**改为页面挂载后很快请求，加上超时和后备请求。

## 网站其他页面

- [首页](https://www.imranjan.cn/zh/index.md)
- [关于我](https://www.imranjan.cn/zh/about.md)
- [项目](https://www.imranjan.cn/zh/projects.md)
- [摄影](https://www.imranjan.cn/zh/gallery.md)
- [BYOF Agent](https://www.imranjan.cn/zh/projects/p-byof-agent.md)
- [Shopping Copilot by KI](https://www.imranjan.cn/zh/projects/p-shopping-copilot.md)
- [LCIMS Cloud](https://www.imranjan.cn/zh/projects/p-lcims.md)
- [Look2Act](https://www.imranjan.cn/zh/projects/p-look2act.md)
- [AccessAudit - 链上访问审批与审计系统](https://www.imranjan.cn/zh/projects/p-accessaudit.md)
- [Eye Touch-实时眼动追踪与注视控制系统](https://www.imranjan.cn/zh/projects/p-eyetouch.md)
- [中国高校计算机大赛—移动应用创新赛](https://www.imranjan.cn/zh/projects/p-yigui.md)
- [美莲花餐厅支出管理系统](https://www.imranjan.cn/zh/projects/p-nilufar.md)
- [LED语音交互灯板](https://www.imranjan.cn/zh/projects/p-led.md)
- ["盟升杯"电子设计竞赛](https://www.imranjan.cn/zh/projects/p-msb.md)
- [NUS SOC SWS 2024](https://www.imranjan.cn/zh/projects/p-nus.md)

English version: https://www.imranjan.cn/projects/p-portfolio.md
