去餐厅吃饭,你从来不用管后厨怎么炒菜。你只需要看菜单,说一句“来一份清蒸鲈鱼,微辣”,菜就端上来了。你描述的是结果——要什么菜、什么口味,而不是过程——油温几度、翻炒几下。这种“只描述要什么”的思维方式,在编程里叫声明式。 Three.js 是今天 Web 3D 领域最强大的“后厨”。它能处理几何体、材质、灯光、动画,几乎无所不能。但用原生 Three.js 写场景,你得亲自下厨:创建场景、建几何体、调材质、加灯光、绑动画循环……每一步都要手写,改一个参数就要手动同步一堆状态,就像做十人宴席全要自己颠勺。 React Three Fiber(简称 R3F)就是那张拯救你的“菜单”。它把 Three.js 的后厨能力包装成声明式菜单——你用 JSX 写“场景里有一个旋转的红色立方体”,R3F 负责翻译给后厨执行。前端开发者可以像写 React 组件一样,把 3D 场景拆成可复用的组件树。配合 drei 生态库,灯光预设、轨道控制、模型加载这些“常用酱料包”都给你备好了。本文就从这份“菜单”讲起。
掌握 R3F,等于把 3D 场景开发从“手工作坊”升级为“组件工厂”——前端技能栈直通 Web3D 可视化、AI 产品展示与创意交互开发,职业含金量显著提升。
目录
可视化演示
动态演示React Three Fi的核心机制,通过输入/处理/存储/输出四种模式展示关键概念与交互流程。
本节配有交互动效(进入视口后自动加载,约 3KB)。
React Three Fi:Reac React Three Fib 3D组件化与场景树管理模式 核心机制可视化 React Three Fi整体架构与关键组件
R3F初体验:点菜而不是炒菜
去餐厅吃饭,你不会冲进后厨抢过锅铲,而是拿起菜单说:“来一份清蒸鲈鱼,少放葱。”你只描述要什么菜、什么口味,至于蒸多久、火多大,那是厨师的专业。这种“只要结果、不问过程”的思路,就是声明式思维,也是 React Three Fiber(R3F)改变 3D 开发的底层逻辑。
原生 Three.js 构造 3D 场景,像亲自下厨:先 new THREE.Scene() 起锅,再 new THREE.Mesh() 切菜,每个对象都要手动创建、手动加入场景、手动维护更新。代码一多,后厨就乱成一团。R3F 改写了剧本——你不再发号施令,而是用 JSX 躺平描述“场景里有什么”。
R3F 本质上是一个专为 Three.js 打造的 React 渲染器。它把 Three.js 的每一个类映射成对应的 JSX 元素:<mesh> 对应 THREE.Mesh,<boxGeometry> 对应 THREE.BoxGeometry,<directionalLight> 对应平行光。React 的组件树和 Three.js 的场景图一一对应,React 负责组织结构,Three.js 负责最终绘制。你负责点菜,它负责上菜。
所以用 R3F 写代码,就像在餐厅悠然点单。你拿起“菜单”(JSX),勾选想要的菜(组件),服务员(R3F 渲染器)帮你传给后厨(Three.js)——一顿丰盛的 3D 大餐就此上桌,前后端各司其职。
- React 组件树你用 JSX 写出的嵌套结构,描述场景中有哪些物体以及它们的父子关系。
- R3F 渲染器桥接 React 与 Three.js 的翻译官,把声明式组件树转换为命令式场景图节点。
- 3D 场景屏幕上最终呈现的一切,由 WebGL 根据场景图逐帧绘制输出。
场景搭建:从空桌到满席
一桌宴席从一张空桌子开始。先铺桌布(背景色),再摆碗筷(相机),上冷盘(几何体摆件),开灯(灯光)。每样东西都是菜单上的一道菜,你只管逐个点下去,不必关心后厨怎么备料。R3F 的场景搭建正是这种“按单取菜”的体验。
R3F 中所有场景都装在一个 <Canvas> 容器里,它是整个 3D 世界的“餐桌”。在 Canvas 内部,<mesh> 是“一道菜”,由两部分组成:几何体(形状)和材质(外观)。加一个 <boxGeometry> 就有立方体,换成 <sphereGeometry> 就是球体;材质决定它是哑光塑料还是金属质感,就像决定菜是清蒸还是红烧。
光照是 3D 场景的灵魂。<ambientLight> 是均匀的“环境光”,像餐厅天花板灯,让整体不被漆黑吞没;<directionalLight> 是“射灯”,平行照射产生明暗对比和阴影;<pointLight> 则是“烛火”,从一点向四周发散。相机 <PerspectiveCamera> 决定看世界的视角,像坐在餐桌前的人眼,位置和朝向一变,满桌菜的角度就完全不同。
搭场景就像安排酒席的顺序:先开灯让大厅亮堂,再一道道地上菜。R3F 的每个 JSX 标签就是一张“点菜单”,你按顺序点完,后厨自然把整桌菜摆好。
- mesh 网格物体场景中可见的实体,由几何体和材质两部分组装而成。
- geometry 几何体定义物体的形状,常见的有立方体、球体、圆柱等多种。
- material 材质决定表面如何响应光照,包含颜色、粗糙度、金属感等属性。
- light 灯光为场景提供照明来源,主要类型有环境光、平行光、点光源。
组件化开发:经典菜式反复点
餐厅菜单上的“招牌菜”最受欢迎,因为味道稳定、出菜快。你不会每次都在后厨重新发明一道菜,而是直接下单“照样来一份”。组件化就是把 3D 场景里的“招牌菜”抽出来,批量复用——这是 R3F 给前端开发者最大的礼物。
在 R3F 里,你可以把一块 3D 区域封装成一个 React 函数组件。比如写一个 <Planet position={...} color={...} />,内部包含球体、材质和轨道环线,外部只需要传 props 就能反复使用。这就像厨房里有一个“行星模具”,参数一变,菜就变了:传红色就是火星,传蓝色就是地球。
组件的威力在于嵌套组合:一个 <SolarSystem /> 组件内部可以放置多个 <Planet />。每个组件只关心自己的 props 和渲染结果,父子数据通过 props 单向流动,状态更新由 React 自动触发重渲染。这样,一个上千物体的复杂场景可以被拆解成几十个小而清晰的组件,如同乐高积木自由拼装,每一块都干净利落。
组件化就像标准化菜谱:一道招牌菜被写成标准做法,任何分店都能端出同款味道。你把 3D 世界的“招牌菜”写成组件,一个 <Planet /> 就能点亮整片星空。
- 函数组件一个返回 JSX 的普通函数,接收 props 参数并渲染场景元素。
- props 数据流父组件把数据传给子组件,子组件通过参数读取使用。
- 嵌套组合组件内部再放置组件,一层层形成树形的 3D 场景结构。
- 场景树复用同一个组件在不同位置渲染,产生不同参数的具体实例。
核心Hooks:后厨的传菜铃
后厨里最忙碌的是一排传菜铃。厨师炒好一道菜,按一下铃,传菜员闻声而动,把菜送到对应餐桌。React Hooks 就是这排传菜铃,让 R3F 在关键时刻通知你的组件“该干活了”。没有它,3D 动画就只能靠手动管理一堆回调。
R3F 提供几个核心 Hooks。useFrame 会在每一帧渲染前回调你的函数,是动画的引擎——你不需要手动管理 requestAnimationFrame,只需写“这一帧要旋转多少角度”。useThree 则像一个万能抽屉,让你随时拿到相机、渲染器、场景对象等全局资源,相当于传菜员手里的对讲机。
useFrame((state, delta) => { ... }) 接收两个参数:state 是当前渲染状态,delta 是距上一帧的时间差。做旋转动画时,角度累加等于上一次角度加上角速度乘以时间差,这样在不同帧率下运动速度依然稳定。useLoader 则用来异步加载外部模型和纹理,比如把设计师导出的 GLTF 模型,一行代码塞进场景,省去手动解析的繁琐。
useFrame 就像那排传菜铃,每帧准时响起,让你的组件在正确时刻更新旋转、位移和缩放。你只管听到铃声就去“上菜”,重活全交给 R3F。
- theta 角度物体当前旋转的角度,单位通常为弧度。
- omega 角速度每秒钟转过的弧度数,控制旋转快慢。
- delta 帧间隔相邻两帧的时间差,由 useFrame 自动传入回调。
- 累加更新用上一帧角度加上本帧增量,获得新一帧的角度。
drei生态:现成的酱料包
做饭最省事的办法,是去超市买一包现成的花椒鱼调料。拆开、倒进去、加水煮开,一盘大菜速成。drei 就是 R3F 生态里的“超市酱料包”,把高频需求提前做成了开箱即用的组件,让 3D 开发从“大厨模式”切换成“速食模式”。
drei 是 R3F 官方维护的辅助库,名字取自德语“三”,与 Three.js 的“三”字同源。它收录了几十个高频组件:<OrbitControls> 是“手托相机”的轨道控制器——鼠标拖动就能环绕观察场景,不用自己写一行事件监听;<Environment> 是环境贴图预设,一键获得 PBR 材质的高级光影质感。
常见组件还包括:<Html> 允许在 3D 世界中嵌入普通 DOM 元素,比如悬浮在模型上方的标签;<Text> 用 troika 渲染清晰文本,避免贴图位图的模糊问题;<Float> 让物体做漂浮呼吸动画;<Loader> 提供资源加载进度条。引入这些组件,代码量能砍掉一大半,相当于把社区调试好的“后厨配方”直接抄进自己的菜谱。
drei 让“3D 家常菜”变成“速食料理包”。不想自己剁姜切蒜(手写控制器)、不想熬高汤(手动配光),撕开一包(import 一个组件)就搞定。一包不够?几十种口味任你选。
- 开发时间从零搭建一个 3D 功能的总耗时,包含学习和调试成本。
- 手写时间不借助任何辅助库,从底层 API 开始编码所需的工作量。
- drei 节省时间drei 组件替你把公共逻辑封装好,省下的工时总和。
事件交互:服务员的应变能力
好的服务员眼观六路:你刚抬手,他就递上酒单;你说“这个菜有点咸”,他立刻让后厨调整。3D 场景里的事件交互也需要这种“接得住、反馈快”的能力,R3F 把这份能力变成了 React 开发者最熟悉的 onClick 语法。
R3F 把 Three.js 的底层事件系统封装成 React 的风格,你直接在 <mesh onClick={() => ...}> 上挂回调,就像给 React 按钮绑定事件一样自然。但底层机制和网页点击完全不同:3D 世界里没有 DOM 树,点击检测靠的是“射线检测”——从相机射出一条看不见的光线,看它打中了谁。
射线检测的原理是:从相机经过鼠标位置,向场景发射一条无限长的射线,数学上记作 \(P = O + t\vec{D}\),然后计算这条射线与场景中每个物体的交点。第一个被击中的物体,就是“你点中的那个”。R3F 在内部自动完成这套计算,把命中结果以事件对象的形式传给你的回调,你完全感知不到中间过程。配合 <OrbitControls> 的拖拽、缩放,场景瞬间从“看客式展览”变成“可动手的游乐场”。
你举杯示意(点击),服务员(R3F)通过视线(射线)立刻锁定你要加水的杯子(Mesh),一气呵成。事件交互让 3D 场景不只是好看的“展览馆”,而是可以玩起来的“游乐场”。
- P 射线上的点射线上某个位置的点坐标,随参数 t 变化。
- O 起点射线从相机位置出发,作为检测的原点。
- D 方向由鼠标屏幕坐标反投影得到的世界空间方向向量。
- t 参数沿方向向量走的距离,交点处 t 取具体数值。
性能优化:后厨的高效出菜
饭点高峰,后厨二十个灶同时开火。厨师不会每个菜单独起锅烧油——那样灶根本不够用。聪明的做法是“一锅出”:几个菜的辅料一起炒,再分别装盘。性能优化就是让 3D 后厨在点菜高峰依然出菜飞快,R3F 开发者必须掌握这套“厨房管理术”。
3D 渲染最怕的是“一次渲染调用只画一个物体”。每帧渲染器要逐物体发起 draw call,物体越多,CPU 与 GPU 的通信次数越多,帧率就崩了。R3F 场景中几百个立方体若逐个绘制,帧率会直线下降,如同一个个菜单独开火,灶台根本不够用。
解决方案之一是“实例化网格”(InstancedMesh):同一种几何体与材质,但位置、旋转、缩放各不相同,只用一次渲染调用画完所有实例,像一锅炒出十几份配菜再分装。R3F 中可用 <Instances> 组件声明。另一个技巧是 LOD(细节层级):远处物体用低模,近处用高模,如同“近看精细、远看轮廓”的招牌。此外,组件卸载时记得 dispose 几何体和材质,避免 GPU 内存泄漏,相当于打烊后清理后厨。
高峰期的后厨用“一锅出”稳定出菜,R3F 开发者用实例化、LOD、资源释放让 3D 场景在高负载下依然流畅如初。会优化的人,才撑得起真正的 3D 大场面。
- draw call 次数CPU 向 GPU 发起的一次绘制指令,是性能关键指标。
- 普通渲染开销每个物体单独发起指令,总次数等于物体数量。
- 实例化渲染开销同批次物体合并为一次绘制指令,开销恒定。
- LOD 层级选择根据物体到相机的距离动态切换模型精度。
AI 应用场景
场景 1:AI 模型 3D 可视化仪表盘
阶段:模型分析与结果解读
解决的问题:传统方式用 matplotlib 画静态散点图展示高维 embedding,无法交互、难以探索聚类边界。用 R3F 把十万个数据点渲染成可旋转、可缩放、可点击的 3D 散点图,分析师能直观发现结构。
具体案例:某 NLP 团队把 768 维句子向量用 UMAP 降维到 3D,映射成 R3F 场景中的彩色球体。用户拖动相机查看聚类边界,点击球体弹出对应文本与相似度排序,交互式探索 embedding 空间,模型调优效率提升明显。
场景 2:AI 产品 3D 交互展厅
阶段:产品演示与市场推广
解决的问题:AI 公司的智能机器人、自动驾驶方案难在网页上直观展示。传统 Three.js 命令式代码改版成本高,R3F 让产品前端用 React 组件化管理 3D 模型,快速迭代。
具体案例:一家自动驾驶初创企业用 R3F 搭建官网 3D 展厅,用户拖动旋转车辆模型,点击部件显示传感器参数。drei 的 OrbitControls 与 Html 标签组件让模型标注零成本实现,开发周期从两个月缩短到两周。
场景 3:AI 训练环境 Web3D 模拟器
阶段:强化学习环境搭建与可视化
解决的问题:强化学习训练需要大量仿真环境。在浏览器里用 R3F 搭建轻量 3D 模拟环境,实时渲染 AI 智能体的决策过程,比传统游戏引擎更轻、更易与 Python 训练后端集成。
具体案例:一个机器人团队在浏览器中渲染机械臂抓取训练画面,R3F 场景中每个关节都是独立组件,useFrame 驱动实时旋转,通过 WebSocket 与 Python 强化学习环境同步状态,观察者能实时查看策略表现。
Python 演示
"""声明式3D场景图模拟器: 演示R3F的核心思想, 用Python复刻组件树到场景图的映射""" import mathfrom dataclasses import dataclass, fieldfrom typing import List @dataclassclass Transform: """位置与缩放变换, 对应R3F中mesh的position与scale""" x: float = 0.0 y: float = 0.0 z: float = 0.0 scale: float = 1.0 @dataclassclass SceneNode: """场景节点: 模拟R3F的一个组件实例, 声明式描述场景元素""" name: str kind: str = "mesh" # mesh / light / camera transform: Transform = field(default_factory=Transform) material: str = "standard" children: List["SceneNode"] = field(default_factory=list) def add(self, child: "SceneNode") -> "SceneNode": """声明式添加子节点, 等价于JSX中的嵌套子元素""" self.children.append(child) return child def build_scene() -> SceneNode: """构建太阳系场景, 等价于R3F中渲染一组JSX组件""" scene = SceneNode(name="root", kind="scene") sun = scene.add(SceneNode(name="sun", kind="mesh", material="emissive")) for i in range(3): ang = i * math.pi * 2 / 3 sun.add(SceneNode( name="planet-" + str(i + 1), kind="mesh", transform=Transform( x=math.cos(ang) * 3.0, z=math.sin(ang) * 3.0, scale=0.4 + i * 0.1, ), )) scene.add(SceneNode(name="main-light", kind="light")) return scene def render_scene(node: SceneNode, depth: int = 0) -> None: """递归打印场景树, 模拟R3F把组件树翻译为场景图的过程""" pad = " " * depth t = node.transform print(pad + "[" + node.kind + "] " + node.name + " pos=(" + str(round(t.x, 1)) + ", " + str(round(t.y, 1)) + ", " + str(round(t.z, 1)) + ") scale=" + str(round(t.scale, 2))) for child in node.children: render_scene(child, depth + 1) def main() -> None: """主入口: 构建并渲染声明式场景""" scene = build_scene() print("=== 声明式场景树 ===") render_scene(scene) if __name__ == "__main__": main()
@dataclass自动生成构造方法,让 SceneNode 像 R3F 组件一样“声明即用”children: List["SceneNode"] = field(default_factory=list)子节点列表,构成场景树的骨架def add(self, child: "SceneNode") -> "SceneNode"声明式挂载子节点,对应 JSX 嵌套子元素sun.add(SceneNode(name="planet-" + str(i + 1), ...))循环批量创建子节点,对应数组 map 渲染组件render_scene(scene)递归遍历场景树,模拟 R3F 渲染器的翻译过程if __name__ == "__main__":可执行入口,保证模块既可导入也可运行
常见误区
误区以为 R3F 是 Three.js 的升级版,一个全新的 3D 引擎,学完 R3F 就不用管 Three.js 了。
正解R3F 只是 Three.js 的 React 渲染器,底层所有几何体、材质、渲染管线仍然是 Three.js 的。就像菜单不是厨房,菜单上的菜最终还是要后厨做。R3F 不替代 Three.js,而是换了一种更符合 React 心智的方式去使用它。做复杂效果时,你依然要查 Three.js 文档。
为何常见:因为用 R3F 写代码全程不出现 new THREE.Mesh(),容易误以为 Three.js 不存在了。
误区很多人觉得 R3F 在 React 每次重渲染时都会重建整个场景,肯定比原生 Three.js 慢。
正解R3F 的渲染器非常轻量,它把 Three.js 对象的创建和销毁交给 React 的协调机制精准控制。组件 props 变化时,它只更新对应属性,不会重建无关对象。就像餐厅改一个人名,不会把整个菜单重印一遍。对绝大多数场景,R3F 的性能开销可以忽略。
为何常见:人们习惯认为“多一层封装就多一层损耗”,忽视了 React diff 机制的精细程度。
误区前端开发者觉得会写 React 就能直接用 R3F 做 3D,不需要理解场景图、相机、材质这些 Three.js 概念。
正解R3F 只是语法糖,核心概念全是 Three.js 的。不理解光照模型就调不出真实的金属质感,不理解相机投影就摆不正视角。就像看菜单不需要会颠勺,但至少要知道“清蒸”和“红烧”的区别。R3F 官方文档也强调:先打好 Three.js 基础,R3F 才会得心应手。
为何常见:JSX 语法和 React 太熟悉,让人低估了底层 3D 概念的学习成本。
误区在 useFrame 回调里直接调用 setState 更新 React 状态,想让组件每帧刷新,结果页面卡成幻灯片。
正解useFrame 每帧执行,而 React setState 会触发整个组件树重渲染,两者叠加等于每帧重建虚拟 DOM,性能灾难。正确做法是:用 ref 直接修改 Three.js 对象的属性(如 rotation),绕开 React 渲染;只有 UI 变化才用 setState。这就像传菜铃要精准按,而不是每秒钟把整栋楼的铃都按一遍。
为何常见:前端习惯“改状态等界面更新”,忘了 3D 世界里直接操作对象实例反而更高效。
误区有人引入 `<OrbitControls>` 后直接报 undefined,以为“import 就能用”,完全没安装 @react-three/drei 包。
正解drei 是独立于 R3F 的第三方包,需要先 npm install @react-three/drei 才能使用。它内含几十个组件,按需引入即可。就像超市酱料包要先付钱买回家,不是餐厅免费附赠的。装好后再 import,一切就顺了。
为何常见:教程常见代码片段里省略安装步骤,新手照抄后自然报错,还以为是自己的代码问题。
面试题
Q1. 在 R3F 中,声明一个立方体应使用什么方式?
- A.直接用 cube 标签声明
- B.调用 Three 构造器创建
- C.mesh 组合几何体与材质← 正确答案
- D.依赖 drei 的 Box 组件
答案:C。R3F 中 <mesh> 是一个容器组件,内部嵌套 <boxGeometry> 指定形状、<meshStandardMaterial> 指定外观,三者组合成完整立方体。就像点一杯奶茶:杯子(mesh)、茶底(geometry)、奶盖(material)缺一不可。
Q2. R3F 在 Three.js 生态中扮演什么角色?
- A.一个新式 3D 渲染引擎
- B.基于 React 的渲染器← 正确答案
- C.浏览器内置 WebGL 库
- D.建模与动画制作软件
答案:B。R3F 是 Three.js 的 React 渲染器,复用 Three.js 的几何体、材质、渲染管线,只是把“命令式创建对象”改成“声明式组件”。菜单不是厨房,R3F 不是 Three.js 的替代品。
Q3. useFrame 回调中的第二个参数 delta 表示什么?
- A.当前动画帧的编号值
- B.鼠标移动的位移距离
- C.与上一帧的时间差← 正确答案
- D.相机到场景的距离值
答案:C。delta 是相邻两帧的时间差(秒)。用它乘以角速度得到本帧旋转增量,保证 60FPS 和 30FPS 下旋转速度一致。就像匀速跑步,步频变慢时步幅要加大。
Q4. 想让用户拖拽旋转相机观察模型,最省力的方式是什么?
- A.手写射线检测交互逻辑
- B.手动修改相机的初始位置
- C.给场景添加一盏环境光
- D.使用 drei 的轨道控制器← 正确答案
答案:D。drei 的 <OrbitControls> 封装了完整轨道控制器,鼠标拖拽旋转、滚轮缩放、右键平移开箱即用。手写射线检测和事件监听既复杂又易错,就像买现成的酱料包,别自己磨调料。
Q5. 关于 R3F 的组件化开发,下列哪些说法正确?
- A.定义自定义组件必须用类
- B.props 给子组件传递数据← 正确答案
- C.组件同一实例只渲染一次
- D.组件嵌套组合复杂场景← 正确答案
答案:BD。R3F 组件就是普通 React 函数组件,props 单向传递数据,支持嵌套组合成复杂场景。class 不是必须的,函数组件更简洁;组件当然可以多次渲染不同实例,如同招牌菜可以被每个客人反复点。
Q6. 实例化网格(InstancedMesh)的主要性能收益是什么?
- A.减少绘制调用的次数← 正确答案
- B.提高纹理贴图分辨率
- C.减小模型文件的体积
- D.加快 JS 脚本的编译
答案:A。实例化网格把同几何体、同材质的多个物体合并成一次 draw call 绘制,GPU 每帧收到的指令从 N 条变成 1 条。就像后厨“一锅出”,效率翻倍。
Q7. R3F 处理 3D 物体点击事件,底层依赖的原理包含哪些?
- A.从相机发射射线检测← 正确答案
- B.依赖 DOM 事件冒泡
- C.鼠标方向向量反投影← 正确答案
- D.需要重力感应传感器
答案:AC。3D 点击事件的底层是射线检测:从相机向鼠标方向发射射线与场景物体求交;鼠标屏幕坐标要经过反投影变成世界空间方向向量。DOM 冒泡和重力感应都不参与这个过程。
Q8. drei 生态库在 R3F 中相当于什么角色?
- A.独立于 R3F 的 3D 引擎
- B.R3F 的辅助组件生态库← 正确答案
- C.负责状态管理的工具库
- D.前端项目打包构建工具
答案:B。drei 是 R3F 生态的辅助组件库,包含 OrbitControls、Environment、Html 等几十个高频组件,需要单独安装。它不是引擎、不是状态管理工具、更不是打包工具,而是“现成酱料包”。
Q9. 关于 useFrame 的性能优化,下列哪些做法正确?
- A.用 ref 直接修改对象属性← 正确答案
- B.每帧调用状态刷新函数
- C.避免帧循环中触发重渲染← 正确答案
- D.用状态更新替代所有 ref
答案:AC。帧循环里应直接用 ref 操作 Three.js 对象(如 mesh.rotation),避免 setState 触发 React 重渲染。每帧 setState 等于把整个组件树重新渲染一遍,帧率必然崩溃。用 ref 就像绕过前台直接和后厨对话,效率最高。
Q10. R3F 中大量重复几何体渲染,最佳实践是什么?
- A.实例化网格批量渲染← 正确答案
- B.每个物体写成独立组件
- C.把 Canvas 画布尺寸加大
- D.直接关闭所有场景灯光
答案:A。大量同几何体重复渲染时,首选 <Instances> 实例化,一次 draw call 画完所有实例。拆成独立组件每条都要单独绘制,加大画布和关灯都是不解决问题的歪招。