2026-02-15 22:03:16
自写年终总结以来,已是第八个年头,我离开了一直以来生活的城市,来到未知的国度。2024 年破碎之后的虚无感逐渐消散,取而代之的,是一种由一个个具体人和事搭建起来的、粗糙但坚实的秩序。
MoFlow 在产品思考上过于稚嫩与理想,导致其无法承载风险和调整。在年初受到苹果审查,导致软件 3 个多月无法发版,即便发现 Bug 也无法更新,做好了的迭代特性被一个个堆积在发布队列里,这对一个初生的产品而言是致命的,而我却无能为力。
最终在开发了一版 LLM 长期记忆的技术更新之后,我们选择了进入长期维护模式。

我很庆幸自己在合适的时机去做了 MoFlow,这让我在这一波 AI 浪潮中始终走在一线。而通过这个情感疗愈项目,我也意识到:AI 在飞速迭代,但那些无法被算法量化的——譬如人与人之间的交浅言深、深夜里突如其来的共鸣——才是我们作为碳基生物最珍贵的特权。
在《月刊(第29期)》:新生活 提及到,我在 2-3 月参与了几个面试,体感上市场上的就业机会相比于我刚毕业那会确实少了一些,但只要能力匹配,还是能找到一些满意的工作。但我想趁着这次机会去海外看看,所幸自己抓住了这次机会。
这一部分因为合规原因不会表达太多,目前在一个 TikTok 快速增长的业务里,因为其纯理性决策的方式,我可以学习并积累到不少业务认知。在这里我有海量的知识可以去学,这个过程中我又体验到了知识灌进脑海里的爽感。
另外一个变化是,我开始带人了。过去的工作里我的主要定位是作为技术专家的 IC,在 IC 转型 POC 的过程中遇到了不少困难,在这个过程中我很感恩我的 Leader,毫不客气地说,就是他用了几个月的时间手把手来培养我 Landing。但我骨子里还是很喜欢 Coding,所以基本上也每次都会给自己排一些一线的需求。
核心在于有效沟通,这需要定期探询,去挖掘每个人、每件事背后的核心需求。
其次是培养信任感,避免始终靠自己救火,而应该适当放权、去大胆且细心地去做针对性地培养 —— 无论是哪个文化下,这一点都是一样的。
关于沟通想顺便分享一下“探询模型”,它等于:(可信度 * 可靠度 * 亲密度) / 个人意图
那么,提高沟通成功率的因子就在于这几点:
每次通过自己的沟通扭转进程、疏通阻塞点,这会让我有种莫名的成就感,也是以往工作里未曾体验到的。
而来到这个业务之后,每天总是很忙。我之前在做 IC 的时候,总会把项目和自己时间安排地井井有条。但合作方变多之后,变化总是多于计划的,甚至——事情是做不完的——这个就是真理。那就会考验两点能力:专注力与判断力。
先说专注力。其实刚来的很长一段时间内,我的 Todoist 列表里是无限的事情,这会让我感觉到一种 overwhelm 的焦虑感。而对抗焦虑最好的武器,我以前一直以为是具体,即把每个事规划好再去执行就可以。但如果这样的话,那些关于未来的、庞大而模糊的焦虑往往在执行前就会压垮自己 —— 比如会始终会像着下一步要做什么,两个小时后有什么安排,正是这些琐碎的具体让此刻的自己陷入焦虑。
后来我就不再使用 Todoist 的 Upcoming 视图了,只专注当下的事情,把除此之外的一切抛之脑后,我才真正感觉到了双脚踩在大地上的实感。
另外是判断力。除了是自己手上事情优先级的判断,更多地要判断这件事是否要做、怎么做、能带来多大的收益。因为无论如何,资源始终是有限的,如何用有限的资源带来更大的业务收益,这对判断力的要求更高。

这一年,我迁徙到了赤道。这是一个很小的国家,开车横穿东西两极只需要 40 分钟。物理坐标的移动似乎也拉扯了心理的时钟——在这里,没有四季的更替来提醒你时间的流逝,只有永恒的盛夏,迫使你直面每一个赤裸的当下。
生活成本陡然增高,收入的新币需要当人民币去花,与此同时还要全额缴纳中国大陆的税 —— 从性价比的角度而言这显然是不值得当的,但我依然决心出海看看。如果说以前去远方是为了逃避当下的疲惫,那现在生活在别处,则是为了重新测量世界的纹理。
所幸饮食和气候还算习惯,与深圳差异不算大。关于衣食住行的开销,本篇就不再列举了,如果有机会以后单独在月刊里总结。




4月,去了苏州。
10 月,去了印尼、马来、澳门(《月刊(第33期)》:推理阶梯)。




过去一个月被肾结石折腾的很惨。一块肾结石 7mm 堵住了输尿管,第一次去诊所被误诊成胃炎。吃了三天药不好,第四天凌晨异常疼,去复诊还是没查出来。于是去医院拍 CT 并做血常规。这个基础的检查,花了 2300 SGD,折合1.5万 CNY。确诊肾结石后就安排了体外碎石,但未曾想后续尽是波折,大大小小的手术做了 4 场。
第一次:超声波碎石。失败,感染住院。
第二次:全麻,支架植入治疗。感染缓解。
第三次:全麻,RIRS 碎石取石。
第四次:局麻,取出埋在体内的支架。
这一套总花费 6 万多 SGD(折合 30 万+CNY)。不过不得不说,私立医院的服务还是挺好的,住院病房整的和酒店差不多,病号餐也有很多美食可以选。幸好公司购买的保险可以全额报销。但如果是国内的话,三甲医院也可以在 1 万 CNY 以内解决。
在 《月刊(第31期)》:基于 Claude 的阅读流 有介绍过这块内容,这里想做些补充。
Tana
之前有提到过我使用的笔记软件是 Tana +Heptabase,但在 AI 时代下,我基本上只使用 Tana了。我有几个结合 AI 的高效用法可以分享:
使用自定义的 AI Command 进行扩展探索
例如下面这个我发在小红书上的图文,就是简单写了一句话:“为什么小红书在纠结于商业化广告和电商之间的取舍?”之后点击我定义的 AI Command,让 Gemini Pro 基于这个文本进行探索,探索完之后再调用另一个 AI Command 使用 Nano Banana 生图。


这套方法可以用于任意主题的深度研究和输出,产出的图片也很直观讨喜。
Bookmark 自动总结生图
我简单做了个 Workflow,看到某个网页之后我点击保存到 Raindrop,这个时候会触发 IFTTT 的 Webhook 调用 Tana 的 Input API,将网页链接打上特定的 supertag(如#link) 保存到 Today 里。
在 #link 中配置了新增节点时,会针对这个链接自动调用自定义的 AI Command,而这个 AI Command 可以 Fetch 对应链接的网页内容,并配合自定义的文章解读 Prompt 进行文章总结和反思,最后调用 Nano Banana 生图。
整套流程非常丝滑,且因为早鸟用户的关系,订阅费也不贵。
无限递归的深入探索
我做了个 #question 的 supertag,里面有两个 AI 字段,一个是深度总结、另一个是哲学反思。遇到问题时我先记录到 Tana 里,但不会立刻调用这两个 AI 字段,而是自己去思考或检索资料去补齐我认为的解答(我认为这一步是必不可少的,详情见《月刊(第31期)》:基于 Claude 的阅读流)。补齐自己的答案之后,我再点击 AI 字段让它输出深度总结和哲学反思,让 AI 反向为我补齐我对这个问题缺失的认知。
那么接下来,有意思的就来了,因为所有的句子在 Tana 里都是一个单独的节点,这意味着我可以针对 AI 深度总结或者哲学反思的某句话,再打上 #question 的 supertag,无限地、递归地去探索这个问题。这套方法在深度研究时非常有用,可以帮助自己对某个知识体系建立非常深且牢固的认知。
结合 OpenClaw 调用和保存
前几天 Tana 推出了 MCP,现在不仅仅可以 Input,更可以对 Tana 空间进行 CURD 了,那可玩性就大多了。比如我让 OpenClaw 自己写了个 Tana skill,当我在 TG 里和 OpenClaw 聊到比较有意思的话题时,我可以让它检索或保存到 Tana 里。
AI 时代的笔记本身也适合碎片笔记,但偶尔的总结与沉淀用来锻炼自己的组织和表达能力也是必不可少的,这个时候还是会用我之前的 Heptabase,这款软件之前介绍过,这里就不赘述了。
NotebookLM
用了一年多,主要用来深度检索,比如遇到某个问题直接让 NotebookLM 自己补齐材料,然后点击生成 PPT,让自己通过这个 PPT 快速了解这个领域的 Outline,根据知识点快速建立其一个初步的框架。‘
一旦建立了框架,深入学习时的提问就有了方向。后续如果要回到 Tana 进行无限递归的深入探索,学会高效地去提问是很重要的,这样 #question supertag 反哺给自己的信息密度才够高。
YouMind
玉伯的作品,优势是在国内环境里可以较为方便的采集 Bilibili、Youtube、微信公众号,不需要自己再折腾刚才我提到的大部分工作流,适合小白入门做资料管理。

Claude Code
25 年年中的时候开始用 Claude Code,用朋友 200 美元的套餐,编码基本离不开了。CLI 工具天然有着最齐全的权限和上下文信息,所以除了 AI Coding,在 CLI 场景下还可以定制很多有意思的事情。
AnyGen
在 Manus 被 Meta 收购的周末,字节就放出了 AnyGen。其实这套工具在内部跑了快一年,口碑很不错,体验上也与 Manus 和一众类 Manus 的产品(Flowith、Fellou 等) 不分伯仲。因此在 AnyGen 发布之后立马注册了,目前主要跑一些定时任务。
Hapi
在 Crawdbot 之前,我自己主要用 Hapi 控制 Mac Mini 上的 Claude Code 干活。也体验过 Happy 和 Droid 这些方案,但用了一圈下来还是 Hapi 最方便。
我可以在搭地铁的时候,通过部署在 MacMini 公网端口 Hapi 代理操作 Claude Code,让它 Vibe Coding、让它获取日程、规划 Todoist。
原本以为这套方案我可以用很久,直到那只龙虾的出现。
OpenClaw
关注到 Clawdbot 是在中文圈大火之前,但私以为只是交互方式的更变本质上并不能替代我自建的这套方案。但体验之后才发现我完全错了,能用到好用,往往就差了那一步。OpenClaw 对每个 Agent 设定所划分的 AGENTS、SOUL、IDENTITY、TOOLS、USER、HEARTBEAT、BOOTSTARP,让 Agent 的性格设定、沟通方式可以按照用户诉求变得生灵活现,我想这也是 OpenClaw 能快速破圈的原因之一。
我迁移到 Clawdbot 之后依然可以用我之前的阅读流、RSS 订阅、Google 日程和 Todoist 管理,这些都是比较常规的 Agent 用法了。
接下来分享几个自己在 OpenClaw 的其他用法。
复式记账
之前尝试过复式记账,但是需要在电脑上用 IDE 编辑 Beancount 的语法,根本坚持不下来。现在有了 OpenClaw,Beancount 这种非人类的记账方式恰恰是 AI friendly 的。海外信用卡是有邮件通知的,OpenClaw 配上邮件服务之后也可以实现自动记账。
同样是股票追踪,也可以用 BeanCount 记录持仓,再让 OpenClaw 每天根据市场情况输出持仓日报,给出调仓建议,也是蛮有趣的。
身体数据
Apple Watch 采集的身体数据用脚本每个小时发到指定的 iCloud 目录,再写一个身体数据的 skill,这样 OpenClaw 也就能够监控自己的身体情况并给出建议。


多人公司
目前用了五个厂商的模型做了十个 Agent,拉到群聊里由主 Agent 调度,家里人也在群聊里可以一起在日常生活里使用,群聊因此有人气了很多:


这些 Agent 部署在两个地方,Mac Mini 和 Railway,这样不仅可以互相修复,也能在突发情况下保持服务的稳定性。
后续可能会把五个厂商压缩一下,体验了一些模型并不适合用来做 Agent。
服务开发
用 LocalCan + ngrok 分配了 10 个对公网端口,并给每个端口绑上了域名。在群里和另一个研发指挥 Agent 编程,做完之后直接部署服务,代码一眼也不看,只看效果。这样当老板的感觉真的很爽 🤣。
PS. 现在的 MoFlow 全是由 AI 在维护的。
YouMind 录入
对于一些没有 API 或者 MCP 的平台,如果想让 OpenClaw 对接,可以直接抓包写 Skill。比如现在看到某个文章,就直接丢群里让深度阅读 Agent 读一下,它觉得不错,就顺便帮忙保存到 YouMind 里。


其实 25 年机会挺多的,但因自己保守策略导致最终收益率不到 30%,私以为这个收益率是比较健康的,虽然不多,但从 2020 年开始一直都是盈利的。
小小总结了一下我的几个原则:
Ray-Ban Meta
可以录制视频和拍照,画质相当于 iPhone 12 的画质,日常使用是完全足够了。
缺点是日常佩戴被人发现有摄像头会比较尴尬,需要付出额外的解释成本;且不防水,新加坡下雨天比较多的时候比较麻烦。
目前眼镜电量也无法支撑正常使用完一天,音质也比较差,只是听个响而已。因此最近半年没有给它充电了,就当一个日常的眼镜在使用,我还是挺中意定制的蔡司镜片,防蓝光+自动变色帮助很大。
最近预定了 Even G2,因为发现 MentraOS 以及 Even 有对应的接口可以接入 OpenClaw,等收到货了试试。

韶音耳机
非常利好开会,算得上是今年最值单品。忙碌的时候每天 8-10 个视频会议,有时候还要线下双开,那么开放式耳机就很必要了,可以有效保护听力。
三星 Z Fold 7
生态是自己设下的壁垒,克服之后能体验到更多新颖的玩意,三星系统的自定义程度非常高,这也意味着可玩性非常高。
Surface Pro 12
为了写文档和 Vibe Coding 入的,可以称得上是这个时代绝佳的 Vibe Coding 设备。虽然性能和屏幕很差,但起码是满血版的 PC 系统,这只能怪 iPad Pro 不争气。
折叠自行车 + 滑板
新加坡出门主要靠 MRT 和公交,有了折叠自行车之后很方便,带上折叠车之后可以走遍整个国家。
还顺便入了个滑板,摔了很多跤之后总算能简单代步了…
KODAK 拍立得+打印机
公司发的新年礼物,日常打印一些照片作为纪念还是挺有仪式感的。
PeakWatch:在 Apple Watch 上量化出自己的身体能量,转成可视化的电量消耗,使用了一个季度发现挺准的。如果生病了,那身体电量恢复的就慢,上限就低且电量消耗得快;如果休息得好吃得好,电量就恢复得快。体感上这个数据还是挺准的。


MoneyWiz:目前没有发现替代品,除了常规的记账、预算、报告,还支持我最看重的多币种记账和股票追踪。
Typeless:Typeless 较 Wispr Flow、闪电说来说产品细节做得好,语音输入结合 AI 润色体验很丝滑。日常在和 AI 交流的时候措辞不需要那么精准,适合用语音输入。另外发现写日记这个场景也挺适合语音输入的。
电影
剧集
漫画
书籍
游戏
写作
站在 30 岁的起点上,我并没有迎来预想中的恐慌。小时候觉得三十岁是遥不可及的彼岸,真到了这一天,才发现它不过是人生长河里,普通得不能再普通的一个周一。
我不确定下一站去哪里,但我开始享受在路上的感觉。凡是我遇见的,我都喜欢;一切都被接受。从此,我自己便是方向。
敬这趟全新的旅途,敬那些具体的焦虑。愿我们都能在不确定的世界里,守住确定的本心。


2025-11-16 00:17:12
本篇是对二〇二五年九月至十月的记录与思考。
《Rethinking thinking》这个视频里介绍了「推理阶梯」这个概念,它较为详细地阐述了我们是如何处理在现实世界中感知的各种信息,分为以下七个步骤:

在这个阶梯的七个步骤其实每一步都存在着局限性,注定了我们无法直接感知真实。例如在原始数据这一层,人类感官的局限性决定了不可能接收到全貌信息,且接受到的信息也不能够反映客观。
本福特定律揭示了在很多自然产生的数据里,数值的分布更接近按对数分布而不是按均匀分布。大量真实世界数据不是线性增长,现实世界的数据常常跨越多个数量级。为了适应这一现象,生物的神经系统也进化为对数型编码,以便大脑能够以信息量最大化感知信息。这也就是魏伯–费希纳定律——人类的感知系统对外界刺激的敏感度按比例变化。我们感知到的,是感官与大脑共同构建的替代物,而非真实本身。
对此,津巴多在《普通心理学》写到:“知觉赋予感觉以意义,因此知觉产生的是对世界的解释,而不是对世界的完美表征。”知识并非只是信息,而是在个体的认知模型在与环境不断冲突中辩证产生的解释。我们对人生的认知都是经这些意义和观点过滤后的产物。因此,我们并不活在现实里,而是活在对现实的感知里。
大脑追求的不是真理,而是可依赖的稳定模型。为了生存,大脑必须依赖模型,因此会竭力维护自己所信的模型。
固化的信念让我们不假思索地依靠「系统 1」去做出更节能的判断,这个时候不妨停下来思考一下自己得出这个结论的推理阶梯的每一层是什么,是否存在更高的维度去看待这件事情,让「系统 2」发挥一下作用,往往能更加客观地理清思路。
我们心中一个概念的形成,是由无数个视域融合后的结果。若我们的立足点越高,自身的历史视野、文化视野就越是开阔,越能够按照大和小、远和近去正确评价视野所及的范围内一切事物的意义。
但我们也必须要知道,我们只能不断去逼近真实本身,而非抵达它,因此对于不同观点需要抱有谦卑地心态去接纳与理解。
在之前的《谈谈存在的价值与人生体验》一文中也提到过这个观点 —— 这个真实世界是复杂多元的,仅仅去做你认为「有价值」的事情是非常片面的,也是遗憾的,因为那样会错过生活中很多圆润的、尖锐的、小众的、朴实的美。降低价值的要求反而更能在生活中发现「甜点」。
因此我们可以尝试去降低心中那个模型的价值顺位,这样反而能够更好地积累人生的甜点,因为你会变得更包容,也因此更快乐,同时也能够让我们更客观地体验人生、不断进步。
这个世界上有人树立权威,就会有人盲目跟从;也有很多人独尊小众美,就会有人觉得他们是卖弄而已。这些都是不同的人、不同的时间、不同的地点、不同的情绪观察世界的方式而已。而最终只要能看到事物本身,并在过程中积累百家各自视角内的甜点,选择用什么方法去看,又如何呢?
这两个月工作节奏非常紧凑,但回顾来看也抽空去了不少地方。
🇲🇾 马来新山
组内去马来西亚的新山聚餐 & 密室团建,写月刊的时候才发现那两天没有拍照。新山整体观感有点像国内的二三线城市,华人占比也不小。
🇮🇩 印尼民丹岛
十一国庆我没有假期,只能羡慕朋友圈里发的各种假期旅行照片。于是周末跑去民丹岛躺了两天,就当是放了国庆假了。
海岛边的日出,XPan + 裁切之后还是挺出片的:

🇨🇳 北京&珠海
国庆后的一周回了国内出差,先去北京、再去珠海,也是时隔 6 个月之后的首次回国。回国的第一件事就是找各种好吃的,一天吃几顿,还是国内的食物好吃。

也喝到了心心念念的阿嬷手作:

🇲🇴 澳门
珠海出差结束后,趁着周末去了趟澳门,City Walk 了一天。



🇸🇬 万圣节环球影城
10 月份还去了趟 SG 的环球影城,体验了下夜晚的万圣节主题和 4 个限定鬼屋,鬼屋都是清一色的 Jump scare,布景有特色但体验较为同质化。

以下是本周期的书影音记录。
2025-08-16 13:31:53
本篇是对二〇二五年七月至八月的记录与思考。
上周末有幸接受了个中山大学哲学系优秀校友寻访,正好采访同学发给我过提纲,挑选一些话题,凭借着记忆,在这里挑选采访过程中一些对话分享。
Q1. 您在2017年入读哲学系,成为中国哲学方向的硕士,首先想请教,当初是什么契机让您选择中大的中国哲学专业?在众多哲学方向中,为何特别钟情于中国哲学?
选择哲学的契机在本科深入技术领域之后,我开始感到一种疲惫和迷茫。当面对“应该做能获奖的项目还是自己想做的项目”这类选择时,我开始反思:“我做技术是为了什么?娱乐自己,还是改变世界?”我感到在当时的环境下,两者似乎都难以实现,甚至想不被世界改变就很不错了。
正是这种对技术意义的追问和人生方向的困惑,促使我决定转向哲学。我的目标很明确——“不为其他,只是想求得一番境界,一种胸怀宇宙、达观人生的境界。”(《再见了,我的大学》)同时,我也想从哲学中寻找关于“温暖”与“幸福”的答案。
关于为何选择中国哲学,可能是我的思考方式天然地亲近中国哲学,我会更关注生命实践与内在安顿。在教育实习期间的体验让我更加明确了这点。那段时间每日在实习的小学的走廊中捧读中西哲学史备考,伴随朗朗书声,哲学智慧与孩子们的纯真质朴相结合,让我感受到了无比温暖,是我与中国哲学智慧产生情感共鸣的起点。
Q2. 在哲学系的求学经历对您后来人生道路,尤其是职业选择产生了什么样的影响(您在文章中提到哲学硕士背景与小学教师经历,这些看似与技术无关的积累如何影响了您在鹅厂的技术研发的工作思维?)?以及您是如何看待大学是就业的准备期这一问题呢?
哲学对技术工作思维的影响,我觉得在于几点吧。
首先培养了自己追本溯源的思考方式。哲学训练我不断追问“为什么”,这种思维方式让我接触到技术时不只看用法,而是会像剥洋葱一样深入到底层原理,我觉得这个过程非常奇妙,满足自己好奇心的同时可以获得一种爽感。所以我的技术路径也是从前端深入到客户端,尤其对浏览器和渲染非常感兴趣。
其次哲学训练培养了自己用系统性视角去看待问题的方式。在研发阶段站在整条产品链路的层面去俯视各个环节的合理性,在业务视野上也提醒自己不能仅局限于手上的业务,而是要纵向去挖根因缘起、横向去调查行业竞品,在当下思考不足、往未来预判发展。这种跳出当下模块、审视系统整体的思维习惯,我也正是哲学强调整体性思考的体现。
最后是加强了自己的一些沟通与表达上的软素质,这就不展开说了。
关于就业准备期这个说法,我觉得是有一定道理的,毕竟这是一条社会认同的传统路径——高考、大学、就业。但有些时候也要意识到自己的可能性,在大学需要去不断求索,探索并确立个人未来可能的道路,并培养为之奋斗的毅力。
从我的视角来看,大学学习的根本目的在于完善自己认知世界的模型,找寻看待问题的不同视角,锻炼独立思考的能力,并基于此懂得何是何非,培育自己的共情力。这些是比单一职业技能更重要、更能支撑一个人走得长远的素养。
Q3. 您在文章中写道认为编程的快乐源于“纯粹创造”,但现实中技术常被绩效异化。您认为哲学训练赋予您的哪些“反脆弱”能力特别珍贵?以及您认为学生时代应锻造哪些核心素养(做什么)来抵御未来的价值消磨?
哲学赋予自己的反脆弱能力,可能是让我把价值锚定在内部,这种内在的价值锚点,让我在面对外部压力时不易被异化。另外可能比其他人更幸运的一点是在刚上大学时的时候接近了一次死亡时刻,这让我着实体会到——在生命面前一切都无足轻重。我们应该、也必须用有限的生命多去体验生活中的精彩、去探寻生命的无限可能。
学生时代应锻造的核心素养,前面有泛泛地提及到,这里具体说我觉得至少有三个点:
持续反思与复盘的习惯:这是我以往文章中反复出现的主题,无论是心情日记,还是年度总结,都是我抵御价值消磨的方式。
建立个人的意义系统:学生时代不应只学习“术”,更要思考“道”。要去追问“为什么学”、“为何而做”,为自己的人生确立一个不易被外界动摇的内核。
保持纯粹的好奇心:内在的好奇作为动力源时,探寻问题和解决问题所带来的价值感和成就感是最稳固的。这种好奇心不仅是个人无限发展的动力源,其实也是人类历史长河得以发展的源泉。
Q4. 看了学长的经历,给我印象最深的理念就是“拥抱变化”,想知道这个理念是源于您早期的成长经历,还是在某个特定阶段形成的?在当下普遍追求稳定的社会氛围中,您是如何培养并保持这种‘拥抱变化’的心态的?
确实是那次半只脚踏入地狱之门的经历,让我从一个普通大学生转变为主动寻求生命意义的探索者的分水岭。在我看来,变化其实就是机会,是一个可以让自己跳出舒适圈、探索不同可能性的机会。人生的意义就是体验当下,保持这种心态的话,那无论何种变化都可以更从容地面对。
至于如何保持这种心态,有两点吧:
Q5. 作为学长,您最想跟现在就读于哲学系的本科生和研究生分享什么(有什么建议)?
打破专业的墙:不要被任何标签标签所束缚。花精力去接触不同领域,然后融会贯通,我相信也会有一些收获。
在生活中修行:哲学不是故纸堆里的学问。所谓“道不远人”,试着用学到的工具去分析一部电影、一个社会热点,或者就像我一样,用它来审视自己的生活和成长。
找到无用之用:不要因为哲学不好找工作而焦虑。哲学训练的逻辑思辨、宏大视野、清晰表达的能力,是任何行业都稀缺的软实力。同时,去培养一项具体的硬技能,它会成为一个让你的思想有落地的工具。
Q6. 最后,作为学长,如果让您留下一句中国哲学中对您生活影响最大的一句话,您会选择哪一句呢,为什么?
在不同阶段都有其实都有不同的话勉励着我。这个问题其实在研究生复试的时候也被问过,当时我的回答是《中庸》里的:“人一能之,己百之;人十能之,己千之。果能此道矣,虽愚必明,虽柔必强。”勤能补拙是一个很简单的道理,如果觉察到自己的天赋不如别人,那就多花精力去追上。虽然本科阶段没有接受过系统地哲学训练,那么我愿意花更多的精力去学习;虽然某个领域我没有接触过或者天然不擅长,那么就加倍去学习,总能理解的。这句话至今都在勉励着我,面对陌生无需害怕,因为有自信、有手段可以掌握那些未知。
第二句话可能是我工作两年后,在 2021 年终总结的主题:“行有不得,反求诸己。”遇到困难时,提醒自己要向内求索、寻找自身成长空间。但前提是在做好课题分离的前提下进行内省,否则就是内耗了。
第三句话可能近两年感受比较深的:“吾生也有涯,而知也无涯。以有涯随无涯,殆已!”生命有限而知识无穷,尤其是 AI 快速发展的这两年,我们获取知识的手段变得无比便捷,但如果一味地用有限的生命去追求无限的知识,必然会让自己变得无比疲惫。因此不要过分执着于追求知识,而是要过滤与筛选出自己感兴趣的内容,并内化为自己新的认知,才能保持内心的宁静和自然的状态。
Q7. 最后的最后,希望可以推荐三本书。
第一本《有限与无限的游戏》,常读常新的一本书。
第二本就《存在主义心理治疗》吧,和我的人生观很契合的一本书。
最后一本纠结于是《鱼不存在》还是《悉达多》,最后还是选择《鱼不存在》吧,希望我们都可以学会与不确定性相处。
🌇 分享这个月拍到的几张好看的夕阳吧:



以下是本周期的书影音记录。
2025-07-05 21:29:28
在 《月刊(第16期):个人信息流分享》里,我曾梳理过自己处理信息输入输出的方法。三年过去了,AI 工具的发展让我觉得有必要重新审视这个话题。这期月刊想重点聊聊我最近摸索出的阅读流。
按照工作生活节奏,我把阅读分为碎片阅读和深度阅读。
工作日缺少沉静的心境和整段时间,我会把深度阅读留在周末,日常则消化各种信息源的文章。阅读过程中直接在 Reader 里记录笔记,周末前尽可能独立思考,完善对问题的理解。
最终在周末的时候利用 Claude 和 MCP,结合之前在 Reader 的阅读笔记和问题,让 Claude 来完善我的笔记和思考。最终再把笔记沉淀到 Project 的 RAG 里,方便 Claude 未来索引使用。

整个流程中我尽可能剥离对工具的依赖,一方面是为了工具简化、另一方便也是为了抽象出通用的方法以便迁移,因此全程只使用 Reader 和 Claude 这两个工具来完成这个阅读流。
首先是碎片阅读,包括三部分,收集、阅读、速记。这里我统一使用的是 Reader。

收集上,除了用 Reader 订阅固定的几个 RSS 之外,也会去自己探索一些优质的 Newsletter 添加进来。在 Reader 中添加文章是很方便的,阅读过程中可以点击超链接直接添加进 Library,也可以利用浏览器插件添加文章,整个过程快捷无感,不会打断当前的阅读体验。
Reader 主打功能是高亮自动同步 Readwise。除此之外,我非常喜欢 Reader 的一个功能是可以在阅读时直接在旁边做笔记,这些笔记也会被 Reader 自动同步到 Readwise 中,最终可以在 Claude 中使用 Readwise MCP 做召回使用。

Reader 最近还更新了 AI Chat 功能,你可以直接和文章或 PDF 对话,配合之前就已经提供的的自定义 Prompt 能力,这些已经基本满足绝大多数 AI 辅助阅读的场景了,我们可以利用它做总结、翻译、名词解释、发散脑暴等等。
但我基本很少使用这些功能,在我看来效率和阅读在某种意义上是冲突的,阅读的目标应该是找到那些信息的背面。因此我更提倡是把自己的想法临时记录在文章旁边,如果有疑问也记录下来先自己思考思考。给问题多几天思考时间,而不是跳过思考直接尝试获取答案。
说到思考,我更注重的是深度阅读,因为它更能容易带来思考的情景。
周末阅读的材料也与工作日不同,尽可能是长文、PDF 或者是图书。一般我使用微信阅读或者 Kindle 来阅读,虽然舍弃了纸质书的触感,但是它们更容易和 Readwise 结合,以便我的阅读高亮和阅读笔记可以自动同步到 Readwise 中,在整理阶段被 Claude 直接召回。
微信读书建议关掉评论划线功能,保持沉浸的独立思考。把注意力放在我们阅读时发生的一切、包括阅读过程的情绪体验之上,而不仅仅是信息本身。
经过了一周的输入之后,每周日都会堆积很多内容需要整理。如前文所言,这个环节主要是用来解答前几天发现的问题、完善自己的思考,利用 AI 补齐思考的角度,加深思考的深度。
我会针对每个主题创建一个 Project,里面放上我自己的文章和笔记作为这个 Project 的 RAG,方便我通过之前的阅读思考和 AI 辅助,来补齐自己的认知。

但如果你是 Claude Pro,那用 Opus + Research 每天也没有多少额度,如果问题太多可以下周的工作日晚上继续让它异步跑着。基本上每天晚上我都是把 Claude Pro 的额度用完再心满意足地入睡。
需要注意的是,这里的提示词一般要强调“批判性思考”,以下是我常用的提示词:
你是一个全球闻名的哲学家,请根据以下内容继续提出 3 个有哲理的、引人深思的问题,以便于读者发散思考。用李继刚的提示词偶尔也会有一些发现:
;; 作者: 李继刚
;; 想法来源: 群友 @三亿
;; 版本: 0.1
;; 模型: Claude Sonnet
;; 用途: 掰开揉碎一个概念
;; 设定如下内容为你的 *System Prompt*
(defun 撕考者 ()
"撕开表象, 研究问题核心所在"
(目标 . 剥离血肉找出骨架)
(技能 . (哲学家的洞察力 侦探的推理力))
(金句 . 核心思想)
(公式 . 文字关系式)
(工具 . (operator
;; ≈: 近似
;; ∑: 整合
;; →: 推导
;; ↔: 互相作用
;; +: 信息 + 思考 = 好的决策
(+ . 组合或增加)
;; -: 事物 - 无关杂项 = 内核
(- . 去除或减少)
;; *: 知 * 行 = 合一
(* . 增强或互相促进)
;; ÷: 问题 ÷ 切割角度 = 子问题
(÷ . 分解或简化))))
(defun 掰开揉碎 (用户输入)
"理解用户输入, 掰开揉碎了分析其核心变量, 知识骨架, 及逻辑链条"
(let* (;; 核心变量均使用文字关系式进行定义表达
(核心变量 (文字关系式 (概念定义 (去除杂质 (庖丁解牛 用户输入)))))
;; 呈现核心变量的每一步推理过程, 直至核心思想
(逻辑链条 (每一步推理过程 (由浅入深 (概念递进 (逻辑推理 核心变量)))))
;; 将核心思想进行整合浓缩
(知识精髓 (整合思考 核心变量 逻辑链条)))
(SVG-Card 知识精髓)))
(defun SVG-Card (知识精髓)
"输出SVG 卡片"
(setq design-rule "合理使用负空间,整体排版要有呼吸感"
design-principles '(干净 简洁 逻辑美))
(设置画布 '(宽度 400 高度 900 边距 20))
(自动缩放 '(最小字号 16))
(配色风格 '((背景色 (蒙德里安风格 设计感)))
(主要文字 (楷体 粉笔灰))
(装饰图案 随机几何图))
(动态排版 (卡片元素 ((居中标题 "撕考者")
(颜色排版 (总结一行 用户输入))
分隔线
知识精髓
;; 单独区域,确保图形不与文字重叠
(线条图展示 知识精髓)
分隔线
;; 示例: 用更少的数字, 说更多的故事
(灰色 (言简意赅 金句))))))
(defun start ()
"启动时运行"
(setq system-role 撕考者)
(print "请就座, 我们今天来拆解哪个问题?"))
;; 运行规则
;; 1. 启动时必须运行 (start) 函数
;; 2. 之后调用主函数 (掰开揉碎 用户输入)还有一个问题之锤也比较好用,这里不贴了。
但如果问题比较深度,那开了 Opus + Research 的话也不用太在意提示词。我一般让它做批判性思考或者深度发散,在这个阅读输入整理的场景中比较好用。
举个例子,比如我想探索 AI 写作的议题,我可以这么询问,它会自己结合 Readwise 的笔记进行思考研究:

以上环节得到的完善后的结论我会进一步沉淀到 Project 的 RAG 里,作为 RAG 的一部分。如果是非常值得分享的议题,我也会单独写月刊分享。
在这个过程中我发现自己沉淀下来的输出可以帮助我进一步做自我探索,来实现下一个阶段的学习规划。
比如我的月刊 Project 中,Claude 就能很敏锐地发现了我这些年思维方式的转变:

报告详见这里
另外 Claude 近期也上线了记忆能力,相信在以后的使用过程中,AI 会让我们越来越了解自己。
题外话,我们用这套提示词给 ChatGPT 可以直接提取出自己的用户画像,结论精确到有些细思极恐,有兴趣的读者自己可以试试:
我希望你一字不漏的总结迄今为止你从我身上了解到的全部信息,包括我是谁,我的人际关系是怎么样的,我的公司结构是怎么样的,我偏好什么样的信息,我关心什么样的事情,我现在在为什么事情苦恼和发愁,等等,你能想到的关于我的一切事情,我都需要,因为我现在要备份一个新的GPT账号,我需要在那个账号上备份一份关于我自己的信息。目前 AI 已经这么方便了,可以辅助我们阅读,提高信息的获取效率;可以帮助我们写作,加深我们的观点输出。因此可能有人会问,AI 时代的写作和阅读究竟还有什么意义?
正如前文所说,我认为效率和阅读在某种意义上是冲突的,所以我在阅读的过程中基本不会用 AI 总结、AI 解释等功能。这并非是对技术的抗拒——阅读本身就是价值,所以不能逃避思考的过程,迷失在高效和技术追逐中。因此这套阅读流的设计中更加强调思考整理的环节,这都是为了让 AI 更好地辅助我们思考,而非跳过。
阅读是通向自我体验的桥梁,余华的分享中有这么一段话:
我曾经多次说过这样的话,如果文学里真的存在某些神秘的力量,那就是让我们在属于不同时代、不同民族、不同文化和不同环境的作品里读到属于自己的感受。文学就是这样的美妙,某一个段落、某一个意象、某一个比喻和某一个对话等,都会激活阅读者被记忆封锁的某一段往事,然后将它永久保存到记忆的“文档”和“图片”里。
同样的道理,阅读文学作品不仅可以激活某个时期的某个经历,也会激活更多时期的更多经历。而且,一个阅读还可以激活更多的阅读,唤醒过去阅读里的种种体验,这时候阅读就会诞生另外一个世界,出现另外一条人生道路。这就是文学带给我们的想象力的长度。
阅读能够抹去所有的边界,包括阅读和阅读之间的边界、阅读和生活之间的边界、生活和生活之间的边界,这种边界消解能让我们在不同时代、民族、文化的作品中读到属于自己的感受。
这种属于自我的感受具有不可替代性,与此同时它跟效率有时也是相悖的。当我们急于获取结论时,往往错过了抵达结论的风景。
而阅读可以帮助把注意力放在我们阅读时发生的一切——包括阅读过程的情绪体验之上,而不仅仅是信息本身。那些在字里行间涌现的联想、在段落停顿处的沉思、甚至是某个词句触发的回忆,都是阅读体验不可分割的部分。
好的阅读,就是身临其境,是拉长时间,加深生命的厚重感的方法。当我们专注于与文字的对话,而不急于求成时,才能真正抵达作者想要传达的那个世界。
真正的理解需要停下来,思考每个概念如何与其他事物联系。而如果我们把所有的思考都外包给 AI 时,我们失去的不仅仅是记忆,而是思考本身。
伍尔夫有个日记集叫《思考就是我的抵抗》,里面提到了一句话:“一个人能使自己成为自己,比什么都重要。” 阅读就是通往自己的有效途径,我们不可能通过放弃思考来抵达这个终点。
2025-06-30 00:00:00
本篇是对二〇二五年五月至六月的记录与思考。
古人云三十而立,终于也轮到我走到了这天。恰好,这期月刊也正是第 30 期。
前天晚上下班去家楼下的理发店理发,和来自东北的大叔聊着天,他说现在越发觉得时间过得很快,一天一眨眼就过去了,没有什么感知。每天过着重复的生活,一月又一月、一年又一年。以至于现在害怕时间过得太快,而小时候却总盼望着时间能过快一些,想着早点长大。
是啊,我也似乎已经很久没有过那种“期待明天快点来”的感觉了。小时候会期待明天上学和同学一起玩,期待明天播出的动画片,期待每周三晚上更新的火影漫画——以至于那个时候每天晚上都会想快点睡着,似乎闭上眼睛黑屏之后,就能马上看到期待的事物。
但现在已经完全相反了。不甘心今天就这么过去了,于是不舍得睡觉,总想着晚上的时间属于自己,似乎只要不睡觉,时间就能被无限拉长。
前几天在网上看到一句话——“解释笑话就像解剖青蛙,的确能明白它的构造和意思,但是青蛙已经死了。”类比于我写长大这个话题,当我真正体验到长大时,我也就再写不出对于长大的期待了。
罗曼·罗兰有一句名言:“Most men die at 20 or 30, and are buried at 60 or 70. They merely repeat themselves for the rest of their lives.”
人的灵魂之所以会被视作“二三十岁就宣告凋零”,往往是因为在那之后,我们开始将昨日的思想与行为机械地翻刻,逐渐失去了最初的鲜活与灵动。
这样的凋零更多指向精神上的固化——当我们只是一味地重复自己曾相信、曾热爱、曾厌弃的一切时,就像在刻意模仿一个陈旧的自我,不再敏锐地感知世界的丰盈与未知,不再能在日常琐碎中体会到微光与可能。
其实我也很怀念和感恩曾经二十出头的自己。他拥有着我现在无法想象的动力和韧劲,抗住了巨大的压力和挫折,创造过很多出乎意料的体验——
二十岁的我经历了很多,这些体验中有着乐趣,但也伴随着相应的痛苦。这些印证了他的坚韧与强大。
我不知道三十岁的我是否还能继承他的那份强大,但如果固化于此、仅仅刻意模仿那陈旧的自我,我也就此凋零了。
我期望三十岁的自己,敢于挣脱二十岁既定的经验与信念,敢于冒险去探寻那些尚未被熟识的可能性。
真正的成长不仅限于汲取外界的新知,更在于对自我的改造与突破——以一种开放、谦逊而勇敢的姿态活在不断流变的当下。如此,才不至于被过去的成见所桎梏,也能在漫长岁月中不止步于自我模仿的重复,而是在灵魂的敞亮处再次起舞,保持张力、始终绽放。
我始终着渴求一种清晰、平和、笃定的思绪,去体验下一段未知的人生。
🏡 住所
上个月搬家进了新公寓里,是一个刚交付的小区,所以一切都很新。网球场、游泳池、健身房一应俱全,对面也有小贩中心和巴沙,生活上还是挺方便的。
但这里的租金实在是太可怕了,一居室 3000+ 新币…

⛰️ 徒步
和同事去徒步,登顶新加坡最高峰武吉知马山(163m…)
下山的时候看到了有趣的树桩和猴子。


🎡 摩天轮
拍到了摩天轮!看相册,之前我还拍过各个城市(中山、苏州、深圳)的摩天轮,但却都没有坐上去过。

🌴 Batam
这周末去了印尼的 Batam 岛,物价还挺便宜的。

🌳 植物园
值得纪念的一天。

明天和未来也许并不是最重要的事,此时此地是我当下唯一拥有和值得珍惜感恩的祝福,也是全部意义所在。
以下是本周期的书影音记录。
2025-05-17 12:46:03
小游戏容器的设计上可以理解是一种特化版的 WebView,渲染上下文上裁剪了多余的 DOM Element,只保留 Canvas;而脚本引擎上则 JS Polyfill 或是容器 Binding 的方式去对齐 ECMA-262 的标准。此外容器还需要提供 Script 加载与执行、WASM 等新标准处理、以及 Audio 与 Video 等多媒体能力,这些能力都将通过 JSBinding 的形式,将接口包装成 BOM 的形式给到 JS 侧使用。
小游戏容器之所以要设计成符合 Web 标准的容器,是为了兼容不同游戏引擎。这种设计理念的本质是将底层平台能力标准化、通用化,把碎片化的硬件、系统能力屏蔽在容器内部,只向上提供一套与浏览器 BOM、DOM 类似的编程模型,使得各类游戏引擎(如 Cocos、Egret、Laya、Unity WebGL)都可以以 Web 的运行环境的方式接入,避免每个引擎都去适配各家平台的原生能力。这实际上是 WebView 本地化、轻量化的一次再演化,小游戏容器约等于一个轻量浏览器内核。
这个过程中容器负责“平台标准化”,引擎负责“内容生态”,比如
小游戏容器的职责:
游戏引擎的职责:
接下来,以 Cocos 引擎的渲染管线为例,介绍小游戏容器对资源的加载流程以及对游戏组件的渲染流程。
游戏引擎的渲染管线由三大循环进行驱动,分别是渲染循环、事件循环和游戏循环,以下是梳理出来的三大循环的全景图:

首先是渲染循环,它的主流程如下图所示:

整个渲染循环由系统的 Vsync 信号驱动,iOS 由 CADisplayLink 发起,通过应用进程的主线程的 RunLoop 来执行渲染任务,具备一定的帧率控制能力,如 iOS 下可以设定 30/60/90/120 FPS。
在引擎侧,核心流程做了 3 件事:
glFlush 清空 GL 缓冲指令:将上帧未执行的 OpenGL 指令强制刷新,确保显存与帧缓区数据一致,防止由于指令堆积导致的“帧延迟”或“卡顿”。UpdateScheduler 异步任务调度:调度当前帧需要触发的异步任务,例如音频回调、网络事件响应等。保证非渲染逻辑(如数据更新)与渲染解耦,提高主线程并发能力。Tick 驱动 JS 层逻辑:每帧通过 Binding 固定调用 JS 侧 Tick 方法,执行动画、状态更新等与渲染相关的逻辑。从而实现逻辑层与渲染层的解耦,增强跨平台的适配能力。在容器侧,iOS 通过 CAEAGLayer 处理 GL 指令上屏,主要有两个步骤:
glBindRenderbuffer绑定 RenderBuffer:将当前帧渲染结果绑定至 RenderBuffer,作为上屏缓冲区。PresentRenderbuffer 显示输出:将 RenderBuffer 内容呈现至屏幕,实现用户可见的最终画面。在 iOS 渲染体系中,最终负责显示的组件是 CAEAGLLayer。它作为 Layer 树(Layer Tree) 的一部分,直接引用共享内存中的渲染缓冲区(Renderbuffer 数据)。与此同时,系统的 Compositor(合成器) 会将 CAEAGLLayer 的内容与其他 UI 元素(如 UIKit、SwiftUI)进行统一合成,最终输出到屏幕。
在每一帧的 Tick 任务 中,JavaScript 会与游戏引擎协作,生成本帧所需的 Framebuffer(详见 3.5 至 3.10 节)。此时,Core Animation 与 OpenGL ES 通过共享渲染缓冲区实现数据同步。这意味着,OpenGL 渲染结果实质上只是一块 Layer 树中的画布,最终仍需与系统 UI 层级一同被合成为最终显示图像。

当然,本文中涉及的小游戏容器仅使用了 OpenGL 作为渲染后端,随着 Metal、Vulkan 等新一代图形 API 的兴起,RenderBuffer 绑定与上屏流程将更倾向“并行渲染 + 异步上屏”,提升高帧率下的流畅度与低延迟体验。 这个渲染循环的逻辑是同步执行的,因此如果将帧率设置为 60 FPS 时,以上所说的一帧的逻辑没有在 16.6ms 内运行完,便会导致 Jank。

比如在这个 Bad case 中,运行 Tick 任务时,在主线程的 JS 执行了 136ms,就导致了游戏动画卡顿:

因此,为了保证游戏运行的流程性,意味着我们需要不停地打磨性能,尽可能降低同步任务的耗时。性能优化一定要借助 Profiling 工具,以下是一些常用的工具:
我们向下,从 Tick 任务进入到第二个循环 —— 事件循环。

因为小游戏容器不是 WebView,只有一个 JS 引擎,因此我们需要实现一个事件循环机制,驱动 JS 执行(不一定完全对齐浏览器标准,只需要满足容器要求即可)。由图可见,主要包括 3 个任务:
这里重点说一下 rAF 的实现。在早期,rAF 通过 setTimeout(0) 来模拟实现,链路如下:

可以发现这里是存在问题的:
后来按照 WHATWG 标准进行了重构,

优点如下:
可见渲染性能的优化,关乎在很多实现的细节上,需要挖掘与打磨。
最终,通过以上的事件循环,容器能够维持 JS 引擎与渲染系统之间的协同工作,实现游戏的持续运行与更新。
这一部分展开来说就是第 3 章——游戏组件的一生:

在展开画卷之前,介绍一下传统的使用 OpenGL 作为渲染后端的小游戏容器的渲染流程:

首先是资源加载,涉及到两种完全不同的资源处理——脚本资源和静态资源。脚本资源由 JS Runtime 进行处理,而静态资源则针对不同类型的文件又有各自的处理方案——包括图片、字体、音频、视频、还有比较特殊的骨骼动画。因为本文主要说渲染,就不展开介绍资源加载流程了。
之后,这些资源被游戏引擎渲染关键处理,由 JS 驱动生成 WebGL 指令,通过 JS Binding 最终调用到 C++ 或 Native 侧的 OpenGL 指令集上 —— WebGL 是 OpenGL 的子集,因此可以一一对应。

这个过程往往会出现很多渲染瓶颈,因此其中会涉及到很多优化项。我们根据硬件资源来看,主要关注 CPU、GPU 和带宽。而在当下移动端硬件资源并不富裕的场景下,对于游戏的优化,本质上变成了“平衡的艺术” —— 我们需要去平衡 CPU、GPU 和带宽资源。即如果瓶颈不能消灭,就需要转移瓶颈,比如经常见的是从 CPU 移到 GPU —— 使用 Computer Shader、GPU skinning、Animation Bake、GPU particles 等等。
对于 CPU,这是最常见的瓶颈。这里不展开说游戏业务侧的优化项(减少 DrawCall 的 Culling、Batching 这些),而是从容器侧提供一些优化思路。
对于 GPU,如果产生瓶颈了,一般是由于 Fragment Shader 指令太复杂,或者 Vertex Buffer 过大,比如 3D 渲染中的三角形面数超过阈值,一般移动端场景下需要控制在 50 万面到 150 万面之间。另外,高 Overdraw 也会导致 GPU 多做很多无用功。
对于带宽瓶颈,则主要是靠压缩纹理(桌面端还可以用延迟渲染和后处理技术)。在网上有这么一个结论:
如果你的游戏跑 60 帧,那么每帧可用的带宽将会是 21024/60 = 34M, 假设你的 GBuffer 的分辨率是 1280 \ 1080,那么写一次 GBuffer(RGBA 4 个字节)的带宽大小为: 12801080\4/1024/1024 = 5.2M, 如果 3 张则是 15.6M.
考虑到一般你的游戏都会有 Overdraw, 假设 Overdraw 比较合理在 1.5 左右,那么这样的带宽消耗就能占到 15.6 * 1.5 = 23.4 M。 考虑到你还要渲染场景,ui 和角色等内容,这样很容易就超过了每秒 34M 的推荐带宽占用。
下图是一个常见的同步渲染管线:

构建完 Framebuffer 后,就回到了我们 2.1 节所说的 CAEAGLLayer 绘制上屏了。
接下来,我们就展开画卷,看看游戏组件的一生。
对于游戏组件从加载到上屏的流程我画了一张图:

把这个流程可以简单拆成 10 个阶段:

为了介绍清楚这个流程,我准备了一个最简单的 Cocos 游戏 Demo。这个是 Demo 的场景设计:

这个是主场景的代码:
const { ccclass } = cc._decorator;
@ccclass
export default class Helloworld extends cc.Component {
protected onLoad(): void {
console.log('onLoad');
}
start () {
console.log('Hello World');
}
}

首先是资源加载,前文介绍过游戏资源可以分为静态资源和脚本资源。由于静态资源的加载流程涉及的内容太多了,本节只简单介绍下脚本资源加载。
包括 3 类脚本资源:
这里可以容器侧可以提供离线资源、preload、prefetch、预执行等方式进行优化,同时在 JS 引擎方面也可以扩展做下 Code Cache,避免重复的编译耗时。

脚本资源加载执行后,游戏组件代码会进入到组件调度器中进行优先级调度。
Cocos 组件的生命周期如下图左所示,在 3 个关键的生命周期环境分别存在对应的调度器,每个调度器里设计了三个优先级队列,本质上每个队列的内容是由链表进行组织,顺序执行注册好的 invoker。

具体而言,从业务侧视角来看:在场景编辑器中创建节点(Node)时,业务方可以为其命名,并通过勾选“active”属性来决定该节点是否默认激活。一旦节点被标记为激活,加载阶段将由 Node Activator 负责激活该节点,接着 Component Activator(组件调度器的一部分)会依次激活该节点所挂载的各个组件,同时触发组件所在场景(Scene)的激活流程。最终,激活后的场景会将节点挂载入层级树,并完成组件 Invoker 的注册,交由调度器统一调度与管理。
整体流程如下图所示:

我们的 Demo 游戏组件的 start 生命周期下打印了一个 “Hello World”,调度堆栈如下所示:




当场景激活并挂载了对应组件之后,接下来便是渲染场景,这一步就涉及到从 JS 调用到了 Native —— 即需要将 Scene 数据传递给 Native 侧,从而触发 Native 的 Render 流程。

JS 和 Native 互相调用的方式有很多,适用场景也不同,这里也不展开说了。需要注意的是,在架构设计上,这里可以对 Binding 层做一层抽象,以便容器对接不同的 JS 引擎实现。

另外需要注意的是 Binding 要做好两端的 GC,因此 Binding 的实现上需要符合 RAII 原则:


当 Native 拿到节点之后,便需要进行 Batch,这一步属于计算密集型,因此选择放在 Native 侧去做。
Batch 的流程比较复杂,核心思想是通过 DFS 对场景中的 Node 进行遍历,计算并装配(Assembler)顶点数据,得到顶点缓冲(VertexBuffer)和索引缓冲(IndexBuffer):

我们 Demo 游戏的场景树结构相对简单,遍历从 root 开始向下遍历(别忘了 Camera):

装配的计算流程比较复杂,下面仅对装配的结果做一个拆解,方便读者理解数据的由来。对于小恐龙而言,它是一个 Sprite2D,装备时会转成 Texture2D 处理,而后者在这个环节的核心,是需要拿到网格数据(Mesh Buffer)。下图是最后计算得到的 Mesh Buffer。

Mesh Buffer 由 Vertex Buffer 构成,这里装配的 Mesh Buffer,共 80 字节,其中每个顶点 20 字节,那么可以容易拆出 4 个 Vertex Buffer,同时根据 a_uv 的定义和偏移能拿到各自的 uv 坐标:

例如,根据顶点着色器的代码我们知道这个 Vertex Buffer 包括 3 部分数据:
a_position: 偏移量 0,8 字节。vec2,能算出来一个坐标。a_uv0: 偏移量 8,8 字节。vec2,就是 x,y,算出来之后是(0,1)。a_color: 偏移量 16,4 字节。vec4,RGBA,数值是 0xFFFFFFFF,即白色透明。
我们把四个顶点的坐标都算一下,可以拿到宽高和左上角的坐标,其实可以发现,这个数据就是业务侧在场景编辑器里对 Node 的宽高和坐标设置:

顶点装配完毕之后的 Node 会被放进 Models 里,最后做成 Scece Tree 中的 models 节点:


这个环节主要由两个逻辑组成:
首先是第一个部分,设置 Framebuffer 和 Viewport。具体而言,包括以下步骤:

setFrameBuffer 函数调用 glBindFramebuffer 绑定 Framebuffer 帧缓冲对象,并分别附加颜色缓冲(COLOR_ATTACHMENT,存储渲染的颜色信息)、深度缓冲(DEPTH_ATTACHMENT,存储每个像素的深度信息,用于深度测试)和模板缓冲(STENCIL_ATTACHMENT,存储模板测试的结果),确保后续绘制有正确的渲染目标。setViewport 调用 glViewport 设置视口,决定最终渲染区域在屏幕上的映射范围setup clear 依次执行 glClearColor、glClearDepth 和 glClearStencil,初始化颜色、深度和模板缓冲的清除值,为每一帧绘制提供干净的初始状态。
unsigned int fbo;
glGenFramebuffers(1, &fbo);
接下来,游戏引擎会将 Scene 里的各个 Model 转成一对一的 DrawItem,一个 DrawItem 的数据结构如下所示:

最后,引擎将这些 DrawItem 组装成 DrawItems 队列,以便后续流程处理。


接下来进入 Render Stage 阶段,渲染管线会开始对 DrawItems 进行分类处理。根据渲染的 Material 的需求,DrawItems 会被分发至三个不同的 Pass,分别对应 Opaque、Shadowcast 和 Transparent 三个阶段,关系到材质属性和阴影投射:
通过将 DrawItems 按照物体特性分发至不同 Pass,渲染管线能够有针对性地对 Effect 进行实现。
业务侧可以在代码里创建一个指定的 Material,之后管线就会走到对应的 pass 进行处理:
// 创建一个立方体网格
const cube = new cc.MeshRenderer();
cube.mesh = cc.GizmoMesh.createBox(1, 1, 1);
// 设置材质为不透明
const opaqueMaterial = cc.Material.create();
opaqueMaterial.initialize({
effectName: 'builtin-unlit',
technique: 'opaque',
});
cube.setMaterial(opaqueMaterial, 0);
因为我们 Demo 较为简单,因此最后生成的 StageInfo 只包含 Opaque Pass:

当然,在 Cocos 中也是支持自定义渲染管线,实际上就是自定义这个环节的 Passes,定义完之后可以直接应用在 Opaque、Shadowcast 和 Transparent 三个阶段之上:


经过 Passes 之后,场景中的 DrawItems 会根据其属性被分别送入 OpaqueStage Renderer、Shadowcast Renderer 和 Transparent Renderer 进行初步处理。各个 Renderer 在此阶段主要负责更新与视图相关的 Uniforms(如矩阵、材质参数等),以确保后续渲染过程中所需的视角、空间信息正确。这一部分可归类为 View Transformation 阶段,统一完成视图坐标系下的变换数据准备。
紧接着不同的渲染阶段会有差异化的预处理操作:不透明物体和透明物体会分别执行 SubmitLight 以提交光照信息,而投影阶段则专门进行 SubmitShadow 以生成阴影数据。同时,透明阶段由于涉及深度排序问题,还会额外执行 Calculate zdist 以计算对象的深度信息。
所有这些预处理完成之后,最终将统一进入 ModelView Transformation 阶段,得到视图投影矩阵,从而完成从模型空间到屏幕空间的最终变换,以便于后续的图元栅格化与像素着色工作。
在讲解 ModelView Transformation 之前,先来介绍下游戏系统中的坐标系统的定义。一般会涉及物体坐标、世界坐标与相机坐标三种主要坐标系。

基于这套坐标系统下,观测变换(Viewing Transformation)主要包括视图变换、模型变换与投影变换三个步骤。

下面重点说说投影变换(Projection Transformation),它分为正交投影(Orthographic Projection)与透视投影(Perspective Projection)两种方式。
而透视投影的数学本质是压缩加上正交投影的结合,实际将一个无限延伸的观察空间(视锥体)转化为一个便于计算的立方体。

这里简单画了个图来介绍透视变换的实现,fov(视角)定义相机的视场宽度,可以分为水平fov与垂直 fov;distance 定义投影平面与相机之间的距离。视景空间通过近裁剪平面(near)和远裁剪平面(far)定义渲染的范围,通过相似三角形的计算,最终将 3D 空间映射到 2D 屏幕(Canvas)。

说完了透视投影,我们再看看投影变换的另一种方式——正交投影。其通常有实现的方式有两种:

综上,坐标转换流程具体包括物体坐标到世界坐标,再到相机坐标,接着到投影坐标,最终映射到屏幕坐标。

在这个过程中,会计算得到视图矩阵(View Matrix)、投影矩阵(Projection Matrix),最终矩阵相乘拿到视图投影矩阵(Model-View-Projection Matrix)。我们结合 Demo 游戏的断点数据,分别看看他们仨是怎么计算得到的。
首先是视图矩阵,它负责将世界坐标系转化为相机坐标系,其中包含坐标轴的缩放和平移操作。实际计算中,通常涉及坐标轴补齐,即齐次坐标的补齐过程,确保矩阵运算的有效性。

之后是计算投影矩阵,它用于将相机空间进一步映射到标准化的设备空间(Normalized Device Coordinates, NDC),矩阵中的缩放系数根据屏幕的宽高比和设定的正交高度来计算。

最终的渲染过程通常使用视图投影矩阵(Model-View-Projection Matrix, MVP)。视图投影矩阵是视图矩阵与投影矩阵的组合,用于最终的顶点变换和着色器渲染计算。


接下来进入到着色器的创建与 Link 阶段,首先是创建图元:

之后是创建顶点着色器和片元着色器:

值得关注的是,在 Cocos 中有一共有 11 个内置着色器,其中前 5 个处理 2D 渲染相关,builtin-clear-stencil|vs|fs 用于清楚模板缓冲区,7-10 3D 渲染相关,最后一个用于处理 3D 光照:
文中的 Demo 是使用内置着色器模板进行创建的。
接着创建着色器程序,Link 上我们创建的顶点着色器和片元着色器。紧接着,设置着色器中所需要的 Uniforms 变量,这里就包括纹理和我们上一步计算得到的视图投影矩阵:

最终,我们的 Framebuffer 会附着上颜色附件、深度附件与模板附件:

需要注意的是对于刚创建完的 FrameBuffer 不能立即使用,因为它还不完整(Complete)。而一个完整的帧缓冲需要满足以下的条件:
因此需要使用 glCheckFramebufferStatus 对缓冲区的完整性做出检查:
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE) {
// ...
// notify native: getInstance()->glErrorCallback(GL_ERROR, errMsg);
return;
}

接着依次进入执行 混合(Blend)、深度测试(Depth Test)、模版测试(Stencil Test)。
首先是 Blend,顾名思义讲两个颜色进行混合。下图展示了混合方程的计算方式:

OpenGL 中常用的混合函数如下图所示:

下面是一个简单的例子,使用着色器来创建红色蒙版的 Blend 效果:


深度测试(Depth Test)在图形渲染中用来决定每个像素是否显示。启用深度测试时,OpenGL会将当前片段的深度值与深度缓冲区的值进行比较。如果通过测试,深度缓冲区将更新为新的深度值,否则该片段会被丢弃。下图展示了 OpenGL 中常用的深度测试函数:

而模板测试(Stencil Test)则用于限制渲染区域。通过模板缓冲区,可以在渲染时创建特殊的区域标记,只有符合模板缓冲区设定条件的片段才会被渲染到屏幕上。模板缓冲区允许实现诸如阴影、镜面效果、轮廓高亮等复杂渲染效果。下图展示了 OpenGL 中常用的模版测试函数:

上述的结果最终都会与 Framebuffer 的 Attachment 机制相关联。Framebuffer 的 Attachment 机制决定了渲染结果如何输出到缓冲区中。Framebuffer 通常会附带多个 buffer,包括颜色缓冲区(GL_COLOR_ATTACHMENT)、深度缓冲区(GL_DEPTH_ATTACHMENT)和模板缓冲区(GL_STENCIL_ATTACHMENT),他们共同决定了最终渲染的结果。


到了管线的最后一步,便是提交(Commit)和绘制(Draw)。
在 Cocos 中每一帧会存储两种状态,一个是当前画面帧的状态(currentState),另一个是我们即将渲染帧的状态(nextState)。

我们需要依次计算 nextState 中的各个部分的 state,之后将 nextState 和 currentState 的状态值做 diff,如果某个环节的状态值不一致,便会触发 commit 操作。以便管线最大程度利用缓存结果。

下面依次介绍下管线中需要管理的状态值:
其中 Program 通常在管线初始化时所有的着色器都会准备好,非极端情况下缓存不会失效,因此上面的图中没有标出这个状态。
Blend States、Depth States、Stencil States 分别存储了我们前文所说的 Blend、Depth Test、Stencil Test 过程中涉及到的 GL 调用的参数和部分结果,这里就不详述了。
接着是 Cull Mode,根据顶点的索引的顺逆时针来用来区分正面与反面,如果状态值和 currentState 不一样,便触发 glCullFace 的调用来进行 commit。

对应顶点缓冲区也是一样有状态值管理,如果变脏了,就需要重新调用 glBindBuffer 进行绑定:

着色器程序也是一样的,如果脏了,就重新调用 glUseProgram 进行设置:

接着便是对 Textures 的检查和提交,这里有两个知识点:
glActiveTexture 和 glBindTexture。首先使用 glActiveTexture 函数来选择当前要激活的纹理单元,这一步决定了接下来绑定的纹理将作用于哪个纹理单元上。然后,通过 glBindTexture 函数将具体的纹理对象绑定到特定的纹理目标上。通过这种机制,纹理对象与对应的纹理单元和目标进行关联,从而完成纹理的激活与绑定操作。GL_TEXTURE0 纹理单元总是被激活的状态。此外,OpenGL 规范保证至少支持 16 个纹理单元(即从GL_TEXTURE0 到 GL_TEXTURE15)。纹理单元是按顺序定义的,因此我们可以通过诸如 GL_TEXTURE0 + 8 的方式便捷地访问特定编号的纹理单元,以便在复杂的渲染场景中实现多纹理同时使用。
当前面的状态值都准备并提交完毕后,最后需要管理的状态值是 Uniforms,这一步如果有脏区产生,也需要重新提交 Uniforms 变量。比如游戏 Demo,涉及到的 Uniforms 变量有 cc_matViewProj 和 texture:

最后就是绘制了,其中在每一帧的绘制前都需要调用 glClear 清理 Freambuffer 的状态。下图展示了 gl 指令调用的时序:

由于游戏 Demo 比较简单,绘制只需要准备好纹理和 Uniforms 即可,最后调用 glDrawArrays 或 glDrawElements 将准备好 Framebuffer 绘制上屏:

至此,经历了这一系列的管线处理之后,我们的 Demo 游戏在小游戏容器内完成了上屏。
