MoreRSS

site iconAiring修改

95后,腾讯音乐高级前端工程师,本科毕业于广州大学教育技术学专业,硕士就读于中山大学哲学系。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

Airing的 RSS 预览

月刊(第31期):基于 Claude 的阅读流

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 时,我们失去的不仅仅是记忆,而是思考本身。

伍尔夫有个日记集叫《思考就是我的抵抗》,里面提到了一句话:“一个人能使自己成为自己,比什么都重要。” 阅读就是通往自己的有效途径,我们不可能通过放弃思考来抵达这个终点。

月刊(第30期):写在 30 岁这天

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 岛,物价还挺便宜的。

🌳 植物园

值得纪念的一天。

明天和未来也许并不是最重要的事,此时此地是我当下唯一拥有和值得珍惜感恩的祝福,也是全部意义所在。

🎬 书影音

以下是本周期的书影音记录。

  • 在读:散文 |《世间的小儿女》| ★★★★☆
  • 在读:散文 |《思考就是我的抵抗》| ★★★★☆
  • 在读:文学 |《余华的文学课》| ★★★★☆
  • 在读:小说 |《长安的荔枝》| ★★★★☆
  • 看完:电影 |《死神来了 6》| ★★★☆☆
  • 看完:电影 |《哭声》| ★★★☆☆

游戏组件的一生: 从加载到上屏

2025-05-17 12:46:03

1. 小游戏容器与游戏引擎

小游戏容器的设计上可以理解是一种特化版的 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 本地化、轻量化的一次再演化,小游戏容器约等于一个轻量浏览器内核。

这个过程中容器负责“平台标准化”,引擎负责“内容生态”,比如

小游戏容器的职责:

  • 提供统一的渲染上下文(Canvas/WebGL)。
  • 提供统一的脚本运行时(JS/WASM)。
  • 提供标准化的输入、音频、视频、多媒体 API。
  • 提供网络、存储、支付、分享、广告等平台能力封装。
  • 对接安全沙箱、权限管理、性能隔离等系统层。

游戏引擎的职责:

  • 提供高层抽象的场景管理、物理引擎、动画、资源管理。
  • 提供开发者友好的编辑器、调试工具链。
  • 提供跨平台的组件化开发范式(UI、骨骼动画、粒子系统等)。
  • 管理游戏生命周期、状态同步、渲染调度。

接下来,以 Cocos 引擎的渲染管线为例,介绍小游戏容器对资源的加载流程以及对游戏组件的渲染流程。

2. 游戏引擎中的三大循环

游戏引擎的渲染管线由三大循环进行驱动,分别是渲染循环、事件循环和游戏循环,以下是梳理出来的三大循环的全景图:

2.1 渲染循环 RenderLoop

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

整个渲染循环由系统的 Vsync 信号驱动,iOS 由 CADisplayLink 发起,通过应用进程的主线程的 RunLoop 来执行渲染任务,具备一定的帧率控制能力,如 iOS 下可以设定 30/60/90/120 FPS。

在引擎侧,核心流程做了 3 件事:

  1. glFlush 清空 GL 缓冲指令:将上帧未执行的 OpenGL 指令强制刷新,确保显存与帧缓区数据一致,防止由于指令堆积导致的“帧延迟”或“卡顿”。
  2. UpdateScheduler 异步任务调度:调度当前帧需要触发的异步任务,例如音频回调、网络事件响应等。保证非渲染逻辑(如数据更新)与渲染解耦,提高主线程并发能力。
  3. Tick 驱动 JS 层逻辑:每帧通过 Binding 固定调用 JS 侧 Tick 方法,执行动画、状态更新等与渲染相关的逻辑。从而实现逻辑层与渲染层的解耦,增强跨平台的适配能力。

在容器侧,iOS 通过 CAEAGLayer 处理 GL 指令上屏,主要有两个步骤:

  1. glBindRenderbuffer绑定 RenderBuffer:将当前帧渲染结果绑定至 RenderBuffer,作为上屏缓冲区。
  2. 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 工具,以下是一些常用的工具:

  1. Xcode GPU Frame Debugger:针对 iOS 平台的图形调试工具,能够深入分析 渲染管线级别的性能瓶颈,尤其适合 Metal 与 OpenGL ES 开发场景。
  2. RenderDoc:业界主流的跨平台图形调试工具,支持捕捉帧数据,分析渲染管线各阶段的资源与性能瓶颈,适用于 OpenGL、Vulkan、DirectX 等 API。
  3. inspector.js:Web 端可以使用,便于在 WebGL 场景下分析 DrawCall、着色器与资源绑定等性能数据。
  4. Mali offline shader compiler:https://zhuanlan.zhihu.com/p/161761815,适用于 ARM Mali GPU 的离线着色器编译与分析工具,可用于评估 Shader 复杂度与指令执行成本,优化移动端渲染性能。
  5. Snapdragon Profiler: 抓帧工具,支持统计 Heavy DrawCallOverdraw,帮助识别渲染瓶颈与冗余计算。

2.2 事件循环 EventLoop

我们向下,从 Tick 任务进入到第二个循环 —— 事件循环。

因为小游戏容器不是 WebView,只有一个 JS 引擎,因此我们需要实现一个事件循环机制,驱动 JS 执行(不一定完全对齐浏览器标准,只需要满足容器要求即可)。由图可见,主要包括 3 个任务:

  1. 消费 timer 等宏任务:处理通过 setTimeout、setInterval 等方式注册的定时任务,确保定时逻辑的正确触发。
  2. 消费 rAF 任务:这一步主要是为了驱动 GameLoop 逻辑,游戏主循环通常挂载于 rAF 回调中,用于逐帧更新渲染与逻辑。
  3. 清空当前帧的 Commands:执行渲染命令、界面更新等待处理的指令,完成本帧渲染周期。

这里重点说一下 rAF 的实现。在早期,rAF 通过 setTimeout(0) 来模拟实现,链路如下:

可以发现这里是存在问题的:

  1. 不合规范:是使用 setTimeout 0 模拟的,并非 vsync 直接驱动。
  2. 链路太长:Native 来维护 Timer 队列,等待 vsync 信号消费完之后再回调给 JS。

后来按照 WHATWG 标准进行了重构,

优点如下:

  1. 标准化:vsync 后直接触发 JS 的调用
  2. 开销小:JS 维护 Timers 队列,移除原生层中转的 JSBinding 调用开销。

可见渲染性能的优化,关乎在很多实现的细节上,需要挖掘与打磨。

最终,通过以上的事件循环,容器能够维持 JS 引擎与渲染系统之间的协同工作,实现游戏的持续运行与更新。

2.3 游戏循环 GameLoop

这一部分展开来说就是第 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 这些),而是从容器侧提供一些优化思路。

  • 比如上面的 JS Binding 调用可能会导致瓶颈,那我们可能会去做合批,从两方面去实现,一方面是调用次数合批,做 CommandBuffer 增加吞吐;另一方面可以做调用实现的合并,比如提供 GFX 高级图形库。
  • 还比如一些 JS 同步任务会阻塞主线程,那么就把计算密集型的任务转到 Native 去做。
  • 比如 JS 自身解释执行的执行效率,那就想办法用 JIT 或者 WASM。
  • 再比如 GC 上,也有一些优化的地方。

对于 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 的推荐带宽占用。

下图是一个常见的同步渲染管线:

  1. 应用层提供顶点数据
  2. 构建顶点着色器对顶点进行标准化
  3. 图元装配构建几何图元
  4. 光栅化阶段,将图元离散化为片元,每个片元对应屏幕上的像素区域
  5. 片元着色器对每个片元执行纹理采样、颜色计算、雾效等像素级处理。
  6. 进行测试与混合操作(Alpha、深度、模板测试),并将结果写入帧缓冲区 Framebuffer。

构建完 Framebuffer 后,就回到了我们 2.1 节所说的 CAEAGLLayer 绘制上屏了。

接下来,我们就展开画卷,看看游戏组件的一生。

3. 游戏组件的一生

对于游戏组件从加载到上屏的流程我画了一张图:

把这个流程可以简单拆成 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.1 Load Assets

首先是资源加载,前文介绍过游戏资源可以分为静态资源和脚本资源。由于静态资源的加载流程涉及的内容太多了,本节只简单介绍下脚本资源加载。

包括 3 类脚本资源:

  1. 内置脚本:引擎启动的时候进行加载,包括注册 JS Binding、实现 window 对象(基础的 BOM 和 Canvas DOM 对象)、polyfill 补齐 ES 标准等等。这个脚本内置在容器里,容器启动 JS 引擎的时候直接加载。这一步可以做多实例和预执行,以加快启动速度。
  2. 入口脚本:容器需要一个入口脚本,类似与 Web 里的 HTML,以便引入游戏入口资源。
  3. 动态加载的脚本:由游戏入口资源引入,比如游戏框架代码、游戏包里的 JS 资源等等。

这里可以容器侧可以提供离线资源、preload、prefetch、预执行等方式进行优化,同时在 JS 引擎方面也可以扩展做下 Code Cache,避免重复的编译耗时。

3.2 Component Scheduler

脚本资源加载执行后,游戏组件代码会进入到组件调度器中进行优先级调度。

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

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

整体流程如下图所示:

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

3.3 Render Scene

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

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

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

3.4 Batcher

当 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 节点:

3.5 Setup

这个环节主要由两个逻辑组成:

  1. 设置 Framebuffer 和 Viewport
  2. 将 Scene 里的各个 Model 转成 drawItems 队列

首先是第一个部分,设置 Framebuffer 和 Viewport。具体而言,包括以下步骤:

  1. 通过 setFrameBuffer 函数调用 glBindFramebuffer 绑定 Framebuffer 帧缓冲对象,并分别附加颜色缓冲(COLOR_ATTACHMENT,存储渲染的颜色信息)、深度缓冲(DEPTH_ATTACHMENT,存储每个像素的深度信息,用于深度测试)和模板缓冲(STENCIL_ATTACHMENT,存储模板测试的结果),确保后续绘制有正确的渲染目标。
  2. setViewport 调用 glViewport 设置视口,决定最终渲染区域在屏幕上的映射范围
  3. setup clear 依次执行 glClearColorglClearDepthglClearStencil,初始化颜色、深度和模板缓冲的清除值,为每一帧绘制提供干净的初始状态。
unsigned int fbo;
glGenFramebuffers(1, &fbo);

接下来,游戏引擎会将 Scene 里的各个 Model 转成一对一的 DrawItem,一个 DrawItem 的数据结构如下所示:

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

3.6 Render Stage

接下来进入 Render Stage 阶段,渲染管线会开始对 DrawItems 进行分类处理。根据渲染的 Material 的需求,DrawItems 会被分发至三个不同的 Pass,分别对应 Opaque、Shadowcast 和 Transparent 三个阶段,关系到材质属性和阴影投射:

  • Opaque:用于绘制完全遮挡光线的物体,如墙面、地板、角色模型等。这类物体会首先渲染,通过深度缓冲区(Z-Buffer)完成遮挡剔除,避免后续无效绘制,提升渲染效率。
  • 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 三个阶段之上:

3.7 ModelView Transformation

经过 Passes 之后,场景中的 DrawItems 会根据其属性被分别送入 OpaqueStage Renderer、Shadowcast Renderer 和 Transparent Renderer 进行初步处理。各个 Renderer 在此阶段主要负责更新与视图相关的 Uniforms(如矩阵、材质参数等),以确保后续渲染过程中所需的视角、空间信息正确。这一部分可归类为 View Transformation 阶段,统一完成视图坐标系下的变换数据准备。

紧接着不同的渲染阶段会有差异化的预处理操作:不透明物体和透明物体会分别执行 SubmitLight 以提交光照信息,而投影阶段则专门进行 SubmitShadow 以生成阴影数据。同时,透明阶段由于涉及深度排序问题,还会额外执行 Calculate zdist 以计算对象的深度信息。

所有这些预处理完成之后,最终将统一进入 ModelView Transformation 阶段,得到视图投影矩阵,从而完成从模型空间到屏幕空间的最终变换,以便于后续的图元栅格化与像素着色工作。

在讲解 ModelView Transformation 之前,先来介绍下游戏系统中的坐标系统的定义。一般会涉及物体坐标、世界坐标与相机坐标三种主要坐标系。

  1. 物体坐标系:以物体自身的中心点(anchor 通常设置为(0.5, 0.5))为原点,用于描述物体内部各个部位的位置关系,便于定义复杂物体内部的原子结构关系。
  2. 世界坐标系:则是以整个场景的中心作为原点,用来统一描述场景内所有物体、相机以及光源的位置关系,确保场景整体的空间一致性。
  3. 相机坐标系:以相机的位置作为原点,是为了将 3D 空间转化为 2D 图像,以便进行计算和渲染。

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

  1. 视图变换:可看作是将相机放置到场景中的过程,主要是定义相机的朝向和位置。
  2. 模型变换:对物体进行放置或调整位置、旋转以及缩放等操作。
  3. 投影变换:类似于摄影,通过投影方式,将三维物体的信息映射到二维的屏幕空间。

下面重点说说投影变换(Projection Transformation),它分为正交投影(Orthographic Projection)与透视投影(Perspective Projection)两种方式。

  • 正交投影常用于工程制图软件,不体现远近透视效果;
  • 透视投影广泛应用于游戏、渲染引擎中,能更真实地模拟人眼观察到的空间透视效果。

而透视投影的数学本质是压缩加上正交投影的结合,实际将一个无限延伸的观察空间(视锥体)转化为一个便于计算的立方体。

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

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

  1. 直接舍弃Z坐标,将 3D 物体转化为 2D 物体,直观但无法表现空间深度;
  2. 将观察空间变换成标准的立方体后,利用变换矩阵进行计算。

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

  1. 首先,在编辑器中定义坐标相加的关系,将物体放置到场景中;
  2. 之后,通过视图变换调整相机位置、模型变换调整物体位置;
  3. 再经过投影变换将 3D 空间投射到 2D 空间;
  4. 最后进行坐标系转换,确保渲染到正确的屏幕位置。

在这个过程中,会计算得到视图矩阵(View Matrix)、投影矩阵(Projection Matrix),最终矩阵相乘拿到视图投影矩阵(Model-View-Projection Matrix)。我们结合 Demo 游戏的断点数据,分别看看他们仨是怎么计算得到的。

首先是视图矩阵,它负责将世界坐标系转化为相机坐标系,其中包含坐标轴的缩放和平移操作。实际计算中,通常涉及坐标轴补齐,即齐次坐标的补齐过程,确保矩阵运算的有效性。

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

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

3.8 Link Program

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

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

值得关注的是,在 Cocos 中有一共有 11 个内置着色器,其中前 5 个处理 2D 渲染相关,builtin-clear-stencil|vs|fs 用于清楚模板缓冲区,7-10 3D 渲染相关,最后一个用于处理 3D 光照:

  1. builtin-2d-spine|vs|fs
  2. builtin-2d-graphics|vs|fs
  3. builtin-2d-label|vs|fs
  4. builtin-2d-sprite|vs|fs
  5. builtin-2d-gray-sprite|vs|fs
  6. builtin-clear-stencil|vs|fs
  7. builtin-3d-trail|particle-trail:vs_main|tinted-fs:add
  8. builtin-3d-trail|particle-trail:vs_main|tinted-fs:multiply
  9. builtin-3d-trail|particle-trail:vs_main|no-tint-fs:addSmooth
  10. builtin-3d-trail|particle-trail:vs_main|no-tint-fs:premultiplied
  11. builtin-unlit|unlit-vs|unlit-fs

文中的 Demo 是使用内置着色器模板进行创建的。

接着创建着色器程序,Link 上我们创建的顶点着色器和片元着色器。紧接着,设置着色器中所需要的 Uniforms 变量,这里就包括纹理和我们上一步计算得到的视图投影矩阵:

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

需要注意的是对于刚创建完的 FrameBuffer 不能立即使用,因为它还不完整(Complete)。而一个完整的帧缓冲需要满足以下的条件:

  • 附加至少一个缓冲(颜色、深度或模板缓冲)。
  • 至少有一个 GL_COLOR_ATTACHMENT。
  • 所有的附件都必须是完整的(保留了内存)。
  • 若开启 Multisampling,则每个缓冲都应该有相同的样本数(sample)。

因此需要使用 glCheckFramebufferStatus 对缓冲区的完整性做出检查:

GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE) {
// ...
// notify native: getInstance()->glErrorCallback(GL_ERROR, errMsg);
return;
}

3.9 Blend & Test

接着依次进入执行 混合(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),他们共同决定了最终渲染的结果。

3.10 Commit & Draw Pass

到了管线的最后一步,便是提交(Commit)和绘制(Draw)。

在 Cocos 中每一帧会存储两种状态,一个是当前画面帧的状态(currentState),另一个是我们即将渲染帧的状态(nextState)。

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

下面依次介绍下管线中需要管理的状态值:

  1. Blend States、Depth States、Stencil States
  2. Cull Mode
  3. Vertex Buffer
  4. Program
  5. Textures
  6. Uniforms

其中 Program 通常在管线初始化时所有的着色器都会准备好,非极端情况下缓存不会失效,因此上面的图中没有标出这个状态。

Blend States、Depth States、Stencil States 分别存储了我们前文所说的 Blend、Depth Test、Stencil Test 过程中涉及到的 GL 调用的参数和部分结果,这里就不详述了。

接着是 Cull Mode,根据顶点的索引的顺逆时针来用来区分正面与反面,如果状态值和 currentState 不一样,便触发 glCullFace 的调用来进行 commit。

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

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

接着便是对 Textures 的检查和提交,这里有两个知识点:

  1. 纹理的应用:具体涉及到 glActiveTextureglBindTexture。首先使用 glActiveTexture 函数来选择当前要激活的纹理单元,这一步决定了接下来绑定的纹理将作用于哪个纹理单元上。然后,通过 glBindTexture 函数将具体的纹理对象绑定到特定的纹理目标上。通过这种机制,纹理对象与对应的纹理单元和目标进行关联,从而完成纹理的激活与绑定操作。
  2. 纹理单元:用于表示显卡可以同时管理的多个纹理。默认情况下,GL_TEXTURE0 纹理单元总是被激活的状态。此外,OpenGL 规范保证至少支持 16 个纹理单元(即从GL_TEXTURE0GL_TEXTURE15)。纹理单元是按顺序定义的,因此我们可以通过诸如 GL_TEXTURE0 + 8 的方式便捷地访问特定编号的纹理单元,以便在复杂的渲染场景中实现多纹理同时使用。

当前面的状态值都准备并提交完毕后,最后需要管理的状态值是 Uniforms,这一步如果有脏区产生,也需要重新提交 Uniforms 变量。比如游戏 Demo,涉及到的 Uniforms 变量有 cc_matViewProjtexture

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

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

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

扩展阅读

  • 《GAMES 101》
  • 《计算机图形学入门:3D渲染指南》
  • LearnOpenGLCN

月刊(第29期):新生活

2025-04-18 14:32:37

本篇是对二〇二五年二月至四月的记录与思考。

近况

新年之后有两个多月没有更新月刊了,这篇文章聊一聊自己的近况。

规划

去年 10 月我从 TME 离职,趁着这次 gap,我规划了两条路线,并限定了自己 gap 的时间最长为 6 个月,我需要在 6 个月里完成既定的 PlanA 或 PlanB。其中给了 PlanA 的 MoFlow 3 个月的研发时间和 2 个月的运营测试时间。而如果没有达到初期既定的运营指标,我将暂停这部分的工作,将项目交接给合伙人。正因为时间有限,所以项目前期的开发节奏才会这么紧张。因此哪怕去了大理旅居,也几乎没有出门娱乐,这在我后来的回忆里一直是个遗憾。

PlanB 则是规避创业风险,回归职场。而我一直以来都有出国发展的规划,因此我希望这次就直接找一个 Base 地在海外的工作。那 gap 的最后一个月,就开始进行面试的准备了,我需要在一个月内拿到 offer。

虽然时间短暂,尤其是海外岗位还要准备英语。但我坚信无论何时,关于我们能做什么都有无数个选项。如果我们一遇到事就认为自己做不到,那扇通往无限可能的大门便随即向我们关闭了。我不愿如此,我要尽我所能去触及所有的可能性。

面试

于是年后开始准备面试、投递简历。这是我的第二份工作,因此选择公司的时候比较谨慎。

细细考虑未来的规划之后,我分了 3 个赛道进行投递:海外赛道 TikTok、Apple,管理岗赛道 Bilibili 和 Anker,以及为了对冲风险还投了微信保底。其中 Bilibili 过了,而且 scope 15 个人左右,就没有去面 Anker 了;Apple 只能 Base 北京,且能力不匹配,放弃。

最后的面试结果是 TikTok、Bilibili 过,微信在四面面委面主动放弃,因为那时已经进入了 TikTok HR 面。而且从自己面试过程的体验而言,也是 TikTok > Bilibili > 其他。最后很幸运,能如愿以偿来到 TikTok,拿到了预期的 offer。

离别

TikTok 的岗位 Base 在新加坡,出国的各种手续都由公司包办了,但是对于自己而言还涉及到很多资产要去处理。一些旧的设备能卖的都卖了,但是最中意的 mini 一直没下定决心。最后一个月里天天开着它,去了周边的许多城市,最终还是在出国的前一天将它出售了。直接二手交易卖给了当时我买它的 4S 店,价格还算能接受。

最后一个月回到深圳,见了一些前同事、以前的老同学,还去了一趟苏州和前舍友告了别。算来,在广东待了 11 年,深圳待了将近 6 年。这么长的时光,没有换来多少轰轰烈烈的回忆,以至于离别的时候,心中更多的是伤感。

突然想到《少年巴比伦》里的一句话:“离别总之是伤感的,因为伤感,所以不能用言语来表达,好像春天里绵密的细雨,用肉眼都分辨不出雨丝,不知道该不该打伞。”那时我的状态大约也是如此,不知道自己失去了什么,但是我能意识到那根弦似乎是要断了。

有时候我会对电视剧或小说的结局感到害怕,因此大多数剧集我总是留着最后几集不去看,或者过了很久很久之后有了足够的心情才去选择去看。我害怕一切的结局,但是我同时对结局之后的一切充满期待和希望。

这一篇章属于我的结局走到了这里,那么以后的生活又会是怎么样的呢?

新生活

生活在视界中的人从来不是安处于某处,而是永远在路上。——詹姆斯·卡斯《有限与无限的游戏》

我们永远都在路上,每一个结尾都是一个新的起点,是重新审视自我、重塑故事的机会。在这个过程中,变化是永恒的,我们对自我的理解和定义则是不断发展的。而我对这个世界则一直有深沉的热爱和长久的好奇,而且我幸运地将它们保存到如今。

孤身抵达新加坡、面对即将加入的新职场,尤其是某红书上对于新加坡和 TikTok 的口碑一直很差,我有很多不安。但我面试的时候感受还是不错的,我相信我的感受。我知道这只是我面对不确定性的恐慌,但我知道这也将是一个机遇,我能够从中挖掘出自己的好奇与热爱。生命之于我们,或许意味着不断地将我们自身及所遭遇的一切转化为光与火。

而经过一周的职场体验,我确信这是值得我全身心投入第二段职业生涯的团队。团队氛围其乐融融,上下之间、同事之间不存在任何隔阂,项目之内不存在信息差,如果想学习想成长,大门随时都在那里。

入职第一天晚,团队里一个同学要转岗了,他请客大家一起吃了顿大餐。饭桌上大家的话题让我笑得合不拢嘴。

入职第三天,团队去环球影城团建,路上我跟丢了中国小伙伴们,只能跟着团队里的外国人一起游玩。在排队的时候,大家会很照顾我,哪怕我口语不好、甚至有些听不明白,但大伙也边比划着边和我交流。

入职第四天晚,隔壁服务端组的 ld 请我们吃了肉骨茶,发现不仅是团队里,整个部门的氛围都超乎了预期的好。

今天是入职第五天,Good Friday 的假期,我在宾馆里写着这些文字,回忆起这周的日子,充实且开心着。

詹姆斯·卡斯在《有限与无限的游戏》中说:“如果我要知道自己生命的全部故事,我就必定已经将它译为一种解释。就好像我能成为自己的旁观者,同时看见自己生命的第一幕与最后一幕,好像我能看穿自己生命的全部。如果这样的话,我就不是在生活,而是在表演生活。”

我很庆幸我不知道所有的故事,我还抱有着对未来的可能性和好奇心。我还继续爱着这个世界,我还在这个世界之中,生活着。

庆幸一切。

🌺 生活点滴

以下是本周期内一些瞬间的记录。

🌍 深圳

这是这些年在深圳的足迹,还有很多迷雾没有探索,希望还有机会继续开图。

🌸 苏州

去苏州找了丁同学,之前玩得很好的舍友。前段时间偶然收到了他的来信,互相交流了下这些年的情况,才意识到我们已经很多年没有见面了,于是走之前去找他一次。实名羡慕他的工作和住处…非常大的房子,有自己的电竞房,微软的工作也使他非常闲适。走之前的晚上,一起去玩了个病娇的剧本杀——他是星期一,我是星期二。

✈️ 新加坡的第一天

下午的飞机,到机场被海关关小黑屋了,直到傍晚才放我出来。来到这里之后,所有的一切都是第一次。第二天在公司周边转了转。

另外,office 是个海景楼!(PS. 职场不让拍照,这个在低楼层的食堂拍的…)

⛱️ 团建

第一次去环球影城,运气好,刚来就赶上团建了。

🎬 书影音

以下是本周期的书影音记录。

  • 读完:传记 |《我可能错了》| ★★★★★
  • 读完:小说 |《少年巴比伦》| ★★★★☆
  • 读完:哲学 |《箭术与禅心》| ★★★☆☆
  • 在读:传记 |《小米创业思考》| ★★★★☆
  • 在读:散文 |《世间的小儿女》| ★★★★☆
  • 看完:动漫 |《葬送的芙莉莲》| ★★★★★
  • 看完:电影 |《扬名立万》| ★★★★☆
  • 看完:电影 |《哪吒之魔童闹海》| ★★★★☆
  • 看完:剧集 |《棋士》| ★★★★☆
  • 看完:剧集 |《漂白》| ★☆☆☆☆
  • 玩过:游戏 |《三角战略》| ★★★★☆

2024,告别盛夏

2025-02-01 18:36:34

自写年终总结以来,这已是第七个年头。这一年发生了许多事,在很多方面主动或被动地打破了过往的平衡与常规。勇敢过、彷徨过、迷茫过、失望过、痛苦过,但或许这是好事,毕竟年龄已悄然迈向而立之年。而一切,不破不立。

工作

毕业这五年

这一年的主题是「告别盛夏」,来自于今年的大事件——《再见了,盛夏》。由于对不确定性的向往、对创业项目的期待,以及其他种种原因,我离开了校招毕业待了五年的企业——TME。

说实话,离开是不舍的,无论是当时还是后来,虽有遗憾,但并不后悔。转眼之间,这五年只剩下回忆。

2018 年 3 月,将校招实习的简历投递给了腾讯和蚂蚁,记得当时周五晚投递之后就有人联系我周六电话面试,是 SNG 的 QQ 音乐。面试的问题已然不太记得了,但去年离别时领导和我说,那天捞了我的简历之后他怕我跑了,于是周六早上来公司加班过来面试。他觉得这几年耽误了我,他认为我的前途原可以更宽阔,而我却完全没有类似的懊悔和遗憾,只有相遇的感激。

我很感激自己遇到了这个团队,当初 SNG 的音乐要被拆家成子公司,HR 一再确认我是否要续约,因为校招的合同将不再是腾讯而是 TME;而蚂蚁当年前端网红团队,加上即将上市的风口,大家都趋之若鹜。但在短暂的音乐实习过后,我毅然选择了留下来。我很喜欢当时的团队氛围,有导师的关照、同事之间的交流打趣、产研之间的互相欣赏和平等讨论,仿佛一切还在象牙塔之中,以至于五年后的今天我仍很怀念刚毕业时的状态,现在联系较多的朋友也是那两年团队里的同事们。

晋升的瓶颈与路径

回忆总是美好的,但现实无法如愿。

在工作期间,我愿付出 100% 精力投入,并且我坚信自己可以把手上的项目做到最好。在毕业 2 年半快速升到 T10 之后,我遇到了第一个瓶颈——没有成长型的项目了。

那一年互联网下行,各企业开始裁员。我们团队也因为部门的业务压力,每天的工作都是琐事,需要多线程同时负责数个需求,甚至在研发过程中的业务也会经常发生变化——今天付出加班精力做的项目,说不定明天就突然被砍了。不再安稳,不知道努力的结局,发现不到成长的契机。于是,想办法提升工作效率,努力适应这个氛围的改变。

我之前负责的是偏研究型的项目,所以对于并行处理事务的效率是有所欠缺的,需要有意识地去锻炼,恰好可以利用这个契机去培养自己的多线程处理能力和跨团队沟通能力。但我发现,当我成长到能同时处理好三个需求的时候,这时可能会分配更多的需求下来,仿佛永动机一样,最后手上就变成了七、八个需求并行。

那段时间甚是焦虑,因为之前一直精益求精,但是在并行之后是无法保证每个项目质量的,这个时候我会攻击自己的内心,出了问题之后无法原谅自己的交付质量。

同时,我意识到如果陷入到这种琐碎的漩涡之中,是几乎不会有成长和突破的,我需要自己找一条路破局——既然自己的部门没有项目,那就去别的部门争取项目。平时工作甚多,那就能周末投入,相当于加班帮别的部门打工。同时,自己也在利用业余时间做一些没有立项的技术项目,想着待生米煮成熟饭,那时不立项也得立。最后一年架构调整,我被分到了之前帮忙加班打工的团队,负责新的项目,升到了 T11。

在外人看来,我毕业 4 年升到 T11 似乎是一直有好项目的“运气”;但复盘下来可以发现,其实许多资源都是需要靠自己争取来的。运气只是成长的契机,但不是决定成长高度的因子。如果将一切都归因于运气,那么我们就会轻易忽视掉自己的潜力,误以为路径不可复制,从而丧失持续前进的动力,甚至影响到他人对我们的信任和支持。

树立一个明确的目标之后,剩下的就是想方设法去找到一条可行的路径,抵达它。

创业

MoFlow

离开 TME 之后,我选择创业。在今年 8 月做了一个月的赛道分析和用研之后,召集了几个小伙伴,立项了 AI + 心理的产品 —— MoFlow

关于 MoFlow 的内容写过《AI 心理疗愈应用的探索与实现》介绍分析过,这里不再赘述项目内容了。我期望能够通过这款产品普及大家对于心理健康领域的关注,以鼓励用户进行自我对话、自我成长、自我关爱。

如同在上篇月刊《AI 没有体验世界的能力》中提到的那样:“关注、关心和关爱——这些都是我们会收到的礼物,同时也是我们可以用来赠予他人的礼物,因为这些礼物只有在一种慷慨相待的生活中才会展现其活力。我们学习理论,并不是为了通过吊书袋式的显摆来彰显自己的存在,关键在于如何改变世界。每一次实践背后都闪耀着众多灵魂的力量,这些努力最终都落到实处,为他人带来福祉——这才是我们追求的实践意义。”

研发

通过这个项目,我得以有机会充分去接触、体验、研究、开发各类 AI+ 的应用层产品,算是补足了在职场期间的空缺,同时找到了对于产品、对于研发最初的动心与热爱。

10 月离职后进入研发阶段,产品侧、设计侧、研发侧、LLM 侧、运营侧拉满效率并行,基本上三天一个小周期开发一个功能模块,9-12-6 连轴转。终于在 11 月底如期推出了 TF 内测版,内测阶段也保持着一天一版本的迭代进度,在 12 月底上架了 App Store。

整个项目的工作量对于小团队而言是巨大的,但是大家始终保持着积极的沟通和研发热情,所有人的力量仿佛拧成一股绳,指向同一个方向。虽然劳累,但满怀热情,不知疲惫。

项目上线后各路资方联系,但我们决定不融资,因为产品收入足以支撑它健康的转动起来。因此我们首要目标是将基础数据打磨好,以服务好用户。在收到一些用户的认可的反馈后,我们也由衷地感到温暖:

挑战

在内测阶段的和上线初期,产品暴露了一些问题,我们及时做出调整和修复,比如上线后对于输入模块的调整,让留存得以提升。预留了一个月的打磨阶段,一切仿佛都在往好的方向发展。

但在上线后没几天,我们的其中一个提审版本就一直被 App Review 卡在“正在审核”中了,至今已经被卡了一个多月,Bug 修复无法发布,期间规划的 4 个版本所包含的重要特性也被卡住无法发布……只收到审核员的一句“我们需要更多时间来进行审核”,之后就再也没有下文了。

因为线上版本存在 Bug 我们甚至也没法铺开推量,焦虑、恐慌、不安在团队中弥漫开来。期间我尝试过撤回重提、修改 ASO/描述/截图/标题、备注 Bug 和审核员沟通、邮件联系 Apple Support、电话联系 Apple 客服、找某宝加速渠道、状态查询申请、加急审核申请、内渠加急申请、找 WWDR 咨询,唯一的回复只是“请耐心等待”。查询了开发者论坛上的相关讨论,遇到这种情况基本要被卡 2 个月以上,论坛中充满了吐槽和抱怨。

因为没有做动态化的部分,以及对相关情况的预案不充分,我们只得暂时放慢了功能迭代节奏,将工作重心转到了服务端和 LLM 侧的优化上来。并预计年后,启动全新模块的研发,不再依赖短平快的迭代节奏。但无论如何,上线初期就遇到这种情况,让我感到焦虑万分。

反思

那两周我一直在 MoFlow 中记录、吐槽这件事情,直到某个周一早上,MoFlow 的周报服务给我推送了一条消息,让我逐渐发现自己的问题所在。

“模式上,每当遇到这些超出你控制范围的事情,你的情绪就会受到较大的影响。”

我反思了之后,发现确实如此。一直以来我都追求对事物的完全把控,不允许让工作内容超出自己的掌控,否则我就会不安焦虑、甚至暴躁易怒。包括前文提到的那一年的工作焦虑,也是被强行穿插的诸多琐事积压,导致无法把控自己的项目质量,从而让自己陷入焦虑之中。

如果再进一步往深层剖析,是我自己一直以来都不允许自己停下来,永远被困在对未来的追赶之中。

前两天在给读者的回信中我写到:

我之前一直很恐惧在雾中原地踏步,每一天、每一分、每一秒都恐惧着,于是想拼劲全力抓住流逝的时间,拼命学习、工作,想走出来,尝试去把握住那些“不确定”中的“确定”。走出来之后我才发现,我所恐惧的其实不是“不确定”本身,而是“原地停留”这件事情,这导致我一直活在对「未来」的追逐中。每当我赶上未来时,它在我手中便失去了价值,于是继续去追赶另一个虚无缥缈的未来,精疲力竭。


然而实际上,“不确定”并不可怕、失败也不可怕,重要的是面对这团迷雾的态度——享受挑战,享受探索,享受当下的一切。打磨心性,放慢脚步,“不是目标成就了你,而是你走向目标的每一步成就了你”。正确的方向是在达到目的的过程中,而非目的的达成;不是走入旅馆,而是走向旅馆;不是得到桂冠,而是追求桂冠。

如同《箭术与禅心》中所提到的“当下的真心”,重要的是在当前时刻保持完全的觉知和正念,而不被过去的记忆或未来的期望所干扰。专注于当前的体验和感受,放下自我的投射,去体验一种纯粹的存在状态。

人生不是竞赛,是探索。生命的旅途并非一连串的终点,而是一系列的旅程。每一个目标的实现只是暂时的停靠,而真正的意义在于如何珍藏好每一个当下,走向这些目标。

能量

生活上今年并不如意,所遇种种皆怀痛楚,长这么大第一次没有回家过年。痛楚的部分就不展开说了,反过来分享一下今年搜集到的一些能量光点吧。

来信中的能量

今年的来信比往年要少一些,只有 31 封。一直以来,我都认为回复来信是一件消耗能量的事情,在《AI 心理疗愈应用的探索与实现》中我曾描述过这种回信的体验:

然而坦率地说,在来信数较少的日子里,我会感到轻松许多。每次回复来信时,我都需设身处地,细细品味来信者的文字,努力与之共情——“若我是他们,在如此境遇下,我该如何应对?”随后,我会整理思绪,将自身的能量转化为回信的内容。通常,一封来信需要一到两周的时间才能回复,因为我自己也需要一个宁静的环境,以便做出最恰当的答复。尝试共情已然如此艰难,更难以想象来信者在现实世界的真实体验中会面对多少痛苦。

但后来,我发现自己可能错了。信件的回复并非是消耗,我在回信的过程中也会反思自己的处理方式,比如前文提到的那段回信,剖析了我自己对未来的执念。

此外,有些来信也同时在给予我意想不到的巨大能量。

年初的时候收到过一封读者的“还愿”:

祝您除夕快乐!看到您能朝着自己满意的方向前进,我感到非常高兴。我想借此机会向您表示感谢,感谢您对我的点拨(还有您的博文),告诉了我世上存在这么多的自我实现方式。

无论如何,感谢您能在最开始的回复中点拨了一位迷茫的学子,我也对即将到来的毕业和工作(希望能圆梦省考)充满了期待。希望您能事业有成,一切顺利!

我也很感谢你,当时读来没有更多的感触,但未曾想到时隔一年的信件会在未来的某一刻反哺自己能量。

以及前些日子另一位读者的一段文字:

在工作这块,我在 9 月读完《再见了,盛夏》后,转发给了我的朋友。问他读完什么感受。他和我说,他最大的感受是难过,字里行间的自由和对未来不确定的向往,是他可能这辈子都无法经历的。看到了有人这样生活,于是,开始审视困住自己的这一方天地。

其实这篇文章在博客下有许多留言,但来信的这段话让我的感受更加真切。那段时间我正处于巨大的迷茫之中,忽然收到这封来信,看着这段文字仿佛是看着从我某段人生里撕下的几页日记,又从中拾取了勇气和力量。

以及朋友来信的安慰:

或许当一个人落魄的时候,更应该爱自己。生活是很现实的,很少有人会越过一个人的外在条件去接触心灵。

我之所以喜欢信件的沟通方式,不仅仅异步沟通便于自己安排时间,更重要的是在《月刊(第 18 期):逃离社交网络》 中写到的那样:“写邮件代替细碎聊天也是一个非常不错的方法,可以沉淀自己日常的想法并锻炼表达力,让自己的关注点回归文字本身,赋予语言与情感最真实、细腻的纹理。并且写信本身也是一件较为庄重的事,可以培养生活里的仪式感。”

我不可能从当下的这一点上看到它与将来的关系,但过些年头之后再回头看,两者之间在一些维度上却存在着千丝万缕的联系。正如乔布斯在斯坦福大学毕业典礼中的演讲所说的:“我们不可能从现在这个点上看到将来;只有回头看时,才会发现它们之间的关系。所以你必须相信,那些点点滴滴,会在你未来的生命里,以某种方式串联起来。你必须相信一些东西——你的勇气、宿命、生活、因缘,随便什么——因为相信这些点滴能够一路连接会给你带来循从本觉的自信,它使你远离平凡,变得与众不同。”

他人的善意

除了读者来信中的能量,我也十分感谢这一年遇到的种种善意。

感谢所有的鼓励。 在离职期间得到了老朋友的礼物和鼓励,同时意外收获了一些新朋友,让我在前进的道路上坚定不移。

感谢所有的欣赏。 自从自己出来单干之后,不会再有上级的肯定和同事的反馈了,我对自己所做事情的价值都会画上问号,会考虑很多、质疑很多、否定很多。但是用户的反馈让我明确了它是有意义的、用户的付费让我知道它是可以健康走下去的。

MoFlow 上线前夕,网易云音乐联系到我希望聊一聊合作,视频会议开启的时候我才发现对面坐着的人是两个 VP 和一个部门的负责人,是 1v3 的会议。我们聊了产品的愿景、核心指标的制定、产品可能会面临的问题,一切都很愉快。在会议即将结束之际,我问他们:“可是这些和你们都没有关系呀,在聊之前这也是我所困惑的,没有领域交叉的话,合作对于你们有任何收益吗?”他们说:“合作其实只是一个建立联系的契机,我们看到你的履历和正在做的事情,很欣赏你。认识你,这才是最重要的目的。”我只能打趣道:感谢友商老板的欣赏和肯定。但这一切,确实给予了我很大能量。

感谢所有的际遇。 不仅仅是生活中遇到的人们,还有一些陌生人的留言。这本是一句可有可无的留言,但它的存在让我真切地感受到我们之间的交集:

感谢所有的故事。 在今年《再见了,盛夏》发布之后,许多人留言了自己的故事,让我见识到了人生中各种奇妙的可能性。其中有一条微信,让我感慨良多:

她说“做可以想象的事情才能让自己觉得活着”。钦佩之余,我将此谨记于心。

一口气行文至此,抬头看了下书店桌上的灯光,或是巧合,但更似是命运的低语:

人生天地之间,若白驹之过隙,忽然而已。——《庄子·知北游》

或许分离和死亡都不是最可怕的,可怕的是还没想好怎么过好这一生就已经走到了尽头,不论是挫折还是辉煌,不论是平淡还是精彩,都是生命中最好的礼物。

感恩一切。

书影音

因为是年终总结,这里照例列举下这年的书影音。

剧集

  • 《安娜》:★★★★★
  • 《追风者》:★★★★★
  • 《玫瑰的故事》:★★★★☆
  • 《庆余年 2》:★★★★☆
  • 《千万别回家》:★★★☆☆
  • 《执行法官》:★★★☆☆
  • 《漂白》:★★☆☆☆
  • 《猎冰》:★★☆☆☆
  • 《白夜破晓》:★★☆☆☆
  • 《九部的检察官》:★★☆☆☆
  • 《回响》:★★☆☆☆

电影

  • 《周处除三害》:★★★★★
  • 《我的阿勒泰》:★★★★★
  • 《误杀瞒天记 2》:★★★★☆
  • 《祭屋出租》:★★★★☆
  • 《异形:夺命舰》:★★★★☆
  • 《死亡录像》:★★★★☆
  • 《三大队》:★★★★☆
  • 《默杀》:★★★★☆
  • 《因果报应》:★★★★☆
  • 《飞驰人生 2》:★★★★☆
  • 《一个母亲的复仇》:★★★☆☆
  • 《瞒天过海》:★★★☆☆
  • 《浴火之路》:★★☆☆☆
  • 《惊天大营救》:★★☆☆☆
  • 《哥斯拉大战金刚 2》:★★☆☆☆
  • 《海王 2》:★★☆☆☆
  • 《被我弄丢的你》:★★☆☆☆
  • 《末路狂花钱》:★☆☆☆☆
  • 《维和防暴队》:☆☆☆☆☆

动漫

  • 《葬送的芙莉莲》:★★★★★
  • 《物理魔法师马修》:★★★★☆

阅读

  • 《鱼不存在》:★★★★★
  • 《有限与无限的游戏》:★★★★★
  • 《亲爱的我饱含杀意》:★★★★★(漫画)
  • 《六个说谎的大学生》:★★★★☆
  • 《哲学 100 问:后现代的刺》:★★★★☆
  • 《书写自愈力》:★★★★☆
  • 《不要相信你所想的一切》:★★★★☆
  • 《慢慢变富》:★★★★☆
  • 《箭术与禅心》:★★★☆☆
  • 《心灵书写》:★★★☆☆
  • 《夏日、烟火和我的尸体》:★★★☆☆
  • 《巴菲特教你读财报》:★★★☆☆
  • 《无:生命的最佳状态》:★★★☆☆
  • 《七个证人》:★★☆☆☆

游戏

今年依旧没有怎么玩游戏,看了下只有一款《塞尔达传说:智慧的再现》,没有通关。

写作

新年目标

29 岁,是一个可攻可守的年纪。这是一个充满可能性的年纪,希望在今年的破碎之后,我可以在而立之年重建秩序。

前几天在看《玫瑰的故事》,剧中最后用了一首诗作为结尾,我想把它作为结束语:

我轻松愉快地走向大路
我健康自由

世界,在我面前
长长的褐色的大路,在我面前
指向我想去的任何地方

从此
我不再希求幸福
我自己便是幸福

凡是我遇见的,我都喜欢
一切都被接受

从此
我不受限制
我使我自己自由
我走到我愿去的任何地方

我完全、绝对地主持着我


世界在我面前,指向我想去的任何地方,此后我不再希求幸福,我自己便是幸福。

愿世间万物,都能迎来开花结果之时。

月刊(第28期):AI 没有体验世界的能力

2025-01-01 16:33:56

本篇是对二〇二四年十一月至十二月的记录与思考。

AI 没有体验世界的能力

MoFlow 于昨夜——2024 的跨年夜上线了 App Store,这标识着这三个月的忙碌换得了一个阶段性的结果。于是,我终于有时间来写写月刊了。

在做 MoFlow 这段期间,我被人问到最多的问题是 ——“AI 真的有可能帮助人们实现心理疗愈吗?”在这篇文章中,我想对这个问题做出一个简单的解答。

注:本文中的 AI 均指 LLMs。

Part 1: AI 与人类的区别 —— AI 没有体验世界的能力

谈及 MoFlow,我们不禁会探讨 AI 咨询师与人类咨询师之间的区别。我们说 AI 无法与人“面对面”的交流、无法与受访者之间实现“现实互动” ——比如语气中的细微变化、沉默中的含义、甚至是未曾言说的情感,这些都是基于情感复杂性而诞生的人类特有体验。

人类的认知不仅仅是信息的处理,而是一种通过感官与情感交织而成的体验。我们通过身体感知现实世界、通过情感去赋予事物意义。这种体验赋予了我们一种独特的理解方式,使我们能够在复杂的情境中做出判断和决策。

如果说体验是我们人类与世界互动的直接方式,它是感官的、情感的,是一种即时的存在状态。那么理解则是对这些体验进行反思、分析和整合的过程,是一种理性的活动。

AI 尽管在处理信息和生成文本方面表现出色,但它们缺乏这种体验的维度。AI 可以处理大量的数据,生成看似合理的文本,但它无法体验这些数据所代表的现实世界,它缺乏人类所拥有的直接体验世界的能力。

体验不仅仅是感知信息,它还包括情感、意识和主观性,这些是人类境遇的核心。体验是把人的内在意识与外在事实、个体与社会结合起来的关键(狄尔泰)。

而理解常常被认为是与体验密不可分的。我们通过生活的经历、情感的波动、以及与他人的互动来获得对世界的深刻理解。AI 虽然可以处理大量数据并从中提取模式,但它无法真正“体验”这些数据所代表的现实,无法满足“体验”的时间性和实践性。与此相对的,AI 的“理解”则是一种基于算法的模拟,而非真正意义上的领悟。

Part2: 概念的无限与语言的界限

接着,我们再来谈谈 LLM 中语言表达的问题。

在 2020 年的一篇《从《光·遇》出发,谈谈「游戏美学」》中,我曾写到:

由于人类有声语文符号的局限性,又由于事物属性的无限丰富,不可能有绝对严密的表达,何况情感与事物都是在不断变化发展的,一切的言语表达对事物的历史进程都只能是疲惫的追踪。

巴别塔事件时,上帝通过分化语言从而使人们之间无法沟通,让人类分崩离析。语言的意义并不由上帝规定,而由使用者规定,并且沟通的手段也不仅仅只有语言,还有无数种其他的行为可以拉近人与概念之间的距离。但仅仅只是拉近而已,概念本身是无限的,在《悉达多》中,也强调了语言无法完全传达真理和智慧。

逻辑学中,逻辑是一个对象在一个概念之下。我们可以将任意对象分为性质和个体,例如:

Fa: 孔子是哲学家

那么这个命题中的 a 即是个体常元,表示孔子;而 F 即谓词,描述某个个体是一个哲学家。我们可以发现,对于具体的个体我们无法把握,在所有的艺术表现中,我们把握到的仅仅是某个个体的特征。而我们心中一个概念的形成,就是由这些个无数视域融合后的结果。而我们的立足点越高,自身的历史视野、文化视野就越是开阔,越能够按照大和小、远和近去正确评价视野所及的范围内一切事物的意义。

此外,将主观经验转化为客观语言的过程本身就存在问题。观察者的主观感受是无法被他人完全分享的,因为他人不能成为观察者,也不能拥有与最初观察者完全相同的经验。正如维特根斯坦所说:“语言的界限意味着我的世界的界限”

因此,AI 基于文本的处理和表达方式也必然会导致理解与共情的困难。

Part3: AI 陪伴的寒潭倒影

前文我们谈及情感的真实性源于体验——人类情感的复杂性不仅仅在于其表现形式,更在于其根植于个人经历、文化背景和生物本能。AI 缺乏亲身体验的能力,这种缺失意味着 AI 的所反馈的情感表现极可能是表面的、缺乏深度的。AI 对人类情感的理解可能更像是一种镜像反射,而非源自内心的共鸣。正如一幅画可以模仿自然,但永远无法成为自然本身。

理解或许还可以是逻辑上的、计算上的,而共鸣则需要一种内在的体验和情感上的连接。

但这一切不是理解、不是共鸣,而是如同寒潭中的倒影,冰冷而完美地映射着世界的表象。

在 2019 年我曾写过一篇《人工意识何以可能?》,这篇文章中探讨了 AI 是否可能具备自我意识。文中举例了“中文屋实验”和“哲学僵尸”来论证了人类具有独特的主观体验能力,这种体验是私密的、无法被直接观察的。但我们可以确定的是,目前的 LLMs 缺乏真实的内在体验。

情感是人类体验的核心,它不仅仅是对外部刺激的反应,更是我们内在世界的真实表达。然而,当模拟情感的技术变得无处不在时,我们可能会陷入一种情感的“稀释”,将复杂而深刻的情感体验简化为可复制的模式——这也就是目前市面上各类 AI 陪伴软件的普遍陷阱。

情感的独特性来自于个体的生命经历和文化背景,每个人的情感都是其生活故事的反映,是其与世界互动的结果。我们需要珍视个体经验的独特性,而不是将其归结为普遍的模式。我们可以通过 Prompt 指定 Agent 的背景故事,我们也可以指定它的人际关系、性格、爱好、人生经历等等,但是 AI Agent 没有经验过这一切,于是将一切转成模拟,给用户的情感反馈也沦为表现化、模式化。用户可以在陪伴类软件里创造复数个 Agent,和它们聊天,好奇想看看这种人设具体会怎么应对我们的情感。即便存在模式化的问题,大不了聊腻了就创造下一个。

但我们真正需要让用户培养一种批判性的意识,去辨别哪些情感是我们真实的内心体验,哪些是由外部技术所引导的。我们需要保持人类情感的真实性,这需要我们不断地进行自我反思和自我理解。只有通过深入的自我探索,我们才能真正理解自己的情感来源,并在这个过程中发现情感的真实价值。

在这个情感模拟的时代,我们需要的不仅是单纯陪伴类的产品,更需要对人类情感本质的深刻理解和尊重。只有这样,我们才能在这个复杂的世界中,保持情感的独特性和真实性

Part 4: 在实践中诞生的意义

AI 的情感理解仅仅是模拟,那么人类与 AI 之间的互动是否会因此失去更深的意义?

在人类交往中,意义常常源于情感的真实交流、共鸣和理解。

然而,值得注意的是,意义并不仅仅来自于情感的真实性。意义也可以从功能性、实用性和结果中产生。例如,当 AI 帮助我们解决复杂问题或提升生活质量时,这种互动本身就具有意义,即使情感理解是模拟的。

此外,我们也可以反思人类自身的情感体验。我们的情感是否总是真实的?或者它们是否也受到社会、文化和生物因素的“编程”?如果我们承认人类情感有时也是一种复杂的“模拟”,那么 AI 的模拟情感是否就显得不那么不同?在《人工意识何以可能?》的最后,我曾提到过:

中文屋实验忽视了工程学维度,若真正实施起来必然需要构建一套模型或者函数,符号虽然不具备语义,但输入输出之所以是可预测的,那还是人的意识所赋予的结果。其实在决定模型或者函数之时,就已经构建立起来了形式语义,这就不仅仅是语法的了,而是语义的了。确实,就目前的 NLP 领域研究而言,无论是经验主义进路去构建深度学习模型,还是以理性主义进路去研究形式逻辑,在构成系统的时刻起,其实就已经是语义的了。后者自不必多言,而前者在喂数据时,监督学习自带的标签就是人为所赋予的语义内容。

因此,所谓的“意义”不在于体验、不在于理解,更多地取决于我们如何选择看待和使用这些互动。AI 的情感模拟可以被我们视为一种工具,它可以帮助我们更好地理解自己、促进人类之间的情感交流、甚至在某些情况下提供情感支持。因此,AI 与人类互动的意义,或许并不在于情感的真实性,而在于我们如何赋予这些互动以价值和目的。在探索 AI 的过程中,我们不仅在观察和创造,也在被观察和被重新定义。这种双向的互动或许正是通向真理的必经之路。

Part5: MoFlow 追求的答案

真正的理解和智慧不仅仅是信息的积累,而是通过体验、反思和情感所获得的深刻洞察。这是人类独有的特质,是我们在面对 AI 时应当珍视和保护的核心价值,也是 MoFlow 的产品理念。

在 MoFlow 所有的设计中,我们都围绕着“使用者”为核心,功能中淡化 AI 的存在,仅充分发挥 AI 的工具属性,规避情感陪伴属性。

例如,在 MoFlow 写完自己的经历之后,MoFlow 会默默提取你的想法,并将其中正效价的想法和情绪做显化处理,当你不经意看到这些外显能量时,你会潜移默化地去培养起更加积极的信念。而 MoFlow 鼓励用户进行正面的自我对话。

在《让心智快速成长的方法:提高自己的“主动性”》一文中,有这么一个论述:

“许多人会习惯性地采用消极的自我对话,比如:

  • 自我否定:我不擅长这个领域,所以我最好回避,别去碰它;
  • 自我质疑:这个问题好像很麻烦,是不是超出了我的能力范围?
  • 自我批评:我刚才的言行举止真是太糟了,我怎么会表现这么差?

这些对话看起来并不严重,但大脑是有一个特性的:它会相信不断重复的信息。这看似普通的自我对话,却会因为大脑的重复记忆特性,逐渐形成固定的认知模式。

久而久之,大脑就会相信它们,从而调低对自己的评价,让自己真的变成自己所反刍和念叨的样子。这就会极大地束缚我们的主动性,让我们在面临困难的时候,变得瞻前顾后、畏手畏脚,难以有效行动。因此,要产生改变,最首要的一步,就是把消极对话变成积极的自我对话。比如:

  • 我不擅长这个领域,所以我最好回避,别去碰它→ 我又有机会可以增长经验了。
  • 这个问题好像很麻烦,是不是超出了我的能力范围?→ 我是不是变得更厉害了呢?不如拿这个问题来试一试吧。
  • 我刚才的言行举止真是太糟了,我怎么会表现这么差?→ 我已经比以前有进步了,也许下一次可以做得更好。

这里要特别注意:很多书籍可能会教你「自我暗示」,比如不断告诉自己「我很棒」「我很强大」「我很厉害」—— 但是,这是错的。

为什么呢?研究发现:过于空泛、不够具体的自我暗示,以及大脑本身不相信的自我暗示,不仅是无效的,反而会造成反效果。它反而会把问题凸显出来,让原本没那么严重的问题显得更严重。”

MoFlow 会当你面临一个难题时,笃定地告诉你:“你解决过相似的问题,你有触类旁通的经验,有足够的能力足以去应对它。”“哪怕出错了也没关系,它也能丰富你的生命,成为你新的经验。”

现在的 AI 已经可以通过分析大量数据,揭示出人类在某些情况下的普遍行为模式,帮助我们更好地认识自己和社会。但最终,真正的理解仍需回归到个体的体验与反思中。

AI 的理解是一种工具性的理解,而非存在性的理解。它可以协助我们更好地理解人类境遇的某些方面,但无法替代我们通过亲身体验和内心反思所获得的深刻洞察。因此,MoFlow 中的 AI 设计更多是引导式、启发式的,以此来鼓励用户自我对话、自我成长、自我关爱。

关注、关心和关爱——这些都是我们会收到的礼物,同时也是我们可以用来赠予他人的礼物,因为这些礼物只有在一种慷慨相待的生活中才会展现其活力。

我们学习理论,并不是为了通过吊书袋式的显摆来彰显自己的存在,关键在于如何改变世界。每一次实践背后都闪耀着众多灵魂的力量,这些努力最终都落到实处,为他人带来福祉——这才是我们追求的实践意义。

“AI 真的有可能帮助人们实现心理疗愈吗?”

——“是的,完全可以。”

🎬 书影音

以下是本周期的书影音记录。

  • 读完:传记 |《鱼不存在》| ★★★★★
  • 读完:心理学 |《无:生命的最佳状态》| ★★★☆☆
  • 读完:心理学 |《不要相信你所想的一切》| ★★★☆☆
  • 读完:小说 |《六个说谎的大学生》| ★★★★☆
  • 读完:科普 |《星星离我们有多远》| ★★★★★
  • 看完:韩剧 | 《安娜》| ★★★★★
  • 看完:网剧 |《白夜破晓》| ★★☆☆☆
  • 在读:哲学 |《世界观》| ★★★★☆
  • 在读:心理学 |《知识的错觉》| ★★★☆☆
  • 在读:哲学 |《我们为什么而活》| ★★★★☆