资讯动态

第 5 章 场景装配与本书约定

发布时间:2026/10/17 6:46:05 来源:尧图企业网站定制
5.1 本章要做成什么样前 4 章你已经摸到了 Babylon 的四个核心概念:Engine(GPU 上下文 + 帧调度)、Scene(场景图)、Mesh(网格)、Material(材质)、Camera(相机)、Light(光源)。但它们像散落的零件——你知道每个零件是什么,还不知道怎么把它们装成一台能跑的机器。本章的目标是给你一套可复用的装配模板,以及一组工程约定。读完这章,你不再需要从零拼凑场景;你会有一张清单,按顺序把四件套(网格/材质/相机/灯光)装进去,然后就能跑。具体产出:一个完整的 40 行场景模板(不含注释),包含四件套 + 命名规范 + dispose 生命周期。一张"白屏/黑屏排查表",覆盖新手最常见的 8 种装配错误。一套本书的工程约定:文件命名、变量命名、dispose 纪律、Observable 解绑规则。5.2 场景装配的四件套顺序Babylon 的场景装配有一条隐含的顺序依赖,违背它会导致"明明代码没报错,但画面就是不对"。正确顺序是:相机 → 灯光 → 网格 → 材质理由如下:相机必须最先存在,因为scene.render()的第一件事是"从相机视角看世界"。没有激活相机,引擎直接抛Error: No camera defined(源码scene.pure.js:4640),而且发生在渲染循环启动后的第一帧。真正负责激活的是构造函数,不是attachControl():ArcRotateCamera的第 7 个参数setActiveOnSceneIfNoneActive默认true(arcRotateCamera.pure.js:663,最终落到camera.pure.js:349-350),场景里还没有相机时就把自己写进scene.activeCamera。实测(v9.30)四条边界:① 只new ArcRotateCamera(...)、一个attachControl()都不写,第一帧照样出画面——激活和输入是两件事;② 把第 7 个参数显式传false,scene.activeCamera保持undefined,第一帧立刻抛No camera defined,这才是"写了相机却没激活"的真实成因;③ 场景里已有相机时,后建的相机不会抢走激活(那句判断带 !scene.activeCamera),换相机必须自己scene.activeCamera = another;④attachControl()所在的arcRotateCamera.pure.js全文没有出现过一次activeCamera,所以它不可能"顺手激活"。本书的写法因此是:靠构造函数的默认激活,attachControl()只用来要鼠标交互,不做多余的手动赋值。灯光在网格之前,这是本书的装配习惯:灯先就位,模板照抄下去第一帧就不会出现「材质还没算过光照」的黑屏。但别把它当成引擎的硬限制——像素探针实测(tools/pxprobe.html?ids=syn:after,syn:afterdirty):网格已经渲染到就绪之后再挂HemisphericLight,画面照样从纯黑变亮(meanLuma 0 → 29.7),手动标脏与否结果一样。源码链路也支持这个结论:Scene.addLight()(scene.pure.js:2669-2674)会把新灯推进每个已存在网格的mesh.lightSources并触发_resyncLightSources()(v9 的光源登记表挂在AbstractMesh上,Meshes/abstractMesh.pure.d.ts:537的get lightSources();SubMesh上根本没有这套 API)。所以「后加的灯必须手动刷脏才生效」是错的,不要为此写多余代码。真要在运行期增删灯光、让材质重编译,用的是scene.markAllMaterialsAsDirty(Material.LightDirtyFlag)(Material.LightDirtyFlag = 2,源码Materials/material.pure.d.ts:110;v9 里没有Material.DirtyLights这个成员),第 19 章会讲它的适用场景。网格在材质之前或同时,因为mesh.material = mat是赋值操作,mat 必须先存在。但也可以先建网格、后挂材质——只要确保渲染前挂上即可。注意:如果网格创建时不指定材质,它会使用scene.defaultMaterial(一个内置的灰色 StandardMaterial)。这在调试阶段很方便(快速看到模型轮廓),但在生产代码里应该显式指定材质,避免依赖隐式默认值。材质最后确认,因为它是"涂在网格上的漆",网格不存在时材质无处可挂。但这不是硬性约束——你可以先创建一堆材质存起来,等网格建好再批量挂上去。这种"先准备后装配"的模式在第 33 章加载 glTF 资产时会用到:先预加载所有贴图生成材质,等模型到位后一次性挂载,避免渲染中途频繁切换材质导致 GPU 状态抖动。这条顺序不是硬性约束(Babylon 允许你先建网格再加相机),而是避免隐性 bug 的最佳实践。本书所有示例都按这个顺序写,你也应该养成这个习惯。5.3 完整装配模板下面是一个最小但完整的场景模板,包含四件套 + 命名规范 + dispose 清理。它对应src/chapters/ch05-scene-assembly.ts的核心逻辑:import{Scene,Engine,ArcRotateCamera,HemisphericLight,MeshBuilder,StandardMaterial,Color3}from'@babylonjs/core'importtype{ChapterArgs,ChapterHandle}from'../core/chapter'exportconstmeta={id:'05',title:'场景装配与本书约定',goal:'掌握四件套装配顺序、命名规范、dispose 生命周期',}asconstexportasyncfunctioncreate({engine,overlay,describe}:ChapterArgs):PromiseChapterHandle{constscene=newScene(engine)// ① 相机:ArcRotateCamera 适合调试,自带轨道控制constcamera=newArcRotateCamera('camera-main',-Math.PI/2,Math.PI/2.5,6,Vector3.Zero(),scene)camera.attachControl()// v9 推荐无参写法(附录B 坑 G-06)// ② 灯光:半球光模拟天光,方向光产生阴影consthemiLight=newHemisphericLight('light-hemi',newVector3(0,1,0),scene)hemiLight.intensity=0.6constdirLight=newDirectionalLight('light-dir',newVector3(-1,-2,-1),scene)dirLight.intensity=0.8// ③ 网格:两个基础形状,命名带用途前缀constbox=MeshBuilder.CreateBox('mesh-box',{size:1},scene)box.position.y=0.5constsphere=MeshBuilder.CreateSphere('mesh-sphere',{diameter:0.8},scene)sphere.position.x=2// ④ 材质:StandardMaterial,命名带 mat- 前缀constmatBox=newStandardMaterial('mat-box',scene)matBox.diffuseColor=newColor3(0.2,0.6,1.0)// 蓝色box.material=matBoxconstmatSphere=newStandardMaterial('mat-sphere',scene)matSphere.diffuseColor=newColor3(1.0,0.4,0.2)// 橙色sphere.material=matSphere// 可选:打印场景统计describe([`相机:${camera.name}`,`灯光数:${scene.lights.length}`,`网格数:${scene.meshes.length}`,`材质数:${scene.materials.length}`,])return{scene,dispose(){// 谁创建谁销毁:Scene.dispose() 会级联清理其下的网格/材质/灯光/相机scene.dispose()},}}这段代码只有 37 行(create函数,不含 import 和 meta),但包含了本书后续所有章节都会用到的骨架。逐条解释设计选择:5.3.1 为什么用 ArcRotateCameraArcRotateCamera是本书的默认调试相机,理由有三:自带轨道控制(鼠标拖拽旋转、滚轮缩放),不需要额外写输入处理。机位由alpha / beta / radius三个角度加距离决定,构造函数的第 5 个参数就是注视点(本模板传Vector3.Zero()),保证相机永远"看着"场景中心;运行途中要换注视点才需要camera.setTarget(...)。顺带记住:不存在new ArcRotateCamera(name, 位置向量, scene)这种直接传坐标的重载,想站到某个坐标点,就反推alpha / beta / radius,或者换用UniversalCamera。它的attachControl()在 v9 里支持无参调用(附录B 坑 G-06),代码最简洁。第 20 章会系统讲相机家族,但现在你只需要知道:写示例时用 ArcRotateCamera,做产品时再换 UniversalCamera 或其他。取景半径怎么定。radius没有固定值,它跟着场景尺度走。本书的经验值是radius≈ 最远物体到注视点距离的 2.4~3 倍。2.4 这个下限不是拍脑袋来的。相机默认fov = 0.8弧度(源码出处:Cameras/camera.pure.js:229,视锥尺寸的计算在同文件:93-98),竖直方向上在距离d处能看见2 × d × tan(0.4) ≈ 0.85 × d,也就是半屏高约0.42 × d。要让离注视点R的物体入画,就得0.42 × radius ≥ R,反推就是radius ≥ 2.4 × R。本模板最远的物体在x = 2,2.4 倍是 4.8,取 6 是多留了一点余量。正文和在线演示的radius常常对不上,这是刻意的。两边跑的是两个不同尺度的场景:正文的章末示例要把本章的实验台完整搭起来,演示页则为了打开快、手机上也看得清,用的是缩小型的同款场景。所以数字不同不代表有一边写错了,但也别把正文的数值抄进你自己的小场景——把 95 搬进一个 5×5 的场景里,只会得到一个空镜头。尺度差得最多的六章,对照如下(正文一列取自各章末尾的完整模板,演示一列取自src/chapters/):章正文场景正文radius演示场景演示radius第 6 章向量实验台,箭头最长伸到y = 418同一套箭头,向量缩成(2,1,0)和(1,2,0)8第 7 章三个size: 1的立方体排开,x从 -3 到 3180.8的方块与球,目标点在x = 36第 8 章日-地-月三级,地球轨道半径 836一个父节点挂两个子物体8第 9 章八种构建器的结果两行铺开,x从 -6 到 725小尺寸几何体加一块10×10的地面10第 10 章手写的四棱锥和星形柱摆在x = ±211.2单个手写三角形,只演示 VertexData5第 11 章200×200的森林,五千到一万棵树9510×10里二十棵12最后一行是故意不全入画的:200×200的森林真要按 2.4 倍推,radius得 240 往上,树就缩成马赛克了——森林要给的是"走在林子里",不是"从飞机上看林子"。所以判断取景对不对,标准始终是同一条:你想让读者看见的东西完整入画、边缘没被裁掉;不打算让他一次看完的,就别为了凑公式把相机推到天上。你自己换场景时,先量出最远物体到场景中心的距离,乘 2.4 起步,再按需要收紧或放宽。5.3.2 命名的三条规矩注意上面的命名模式:相机:camera-main(前缀camera-+ 用途)灯光:light-hemi/light-dir(前缀light-+ 类型)网格:mesh-box/mesh-sphere(前缀mesh-+ 形状)变换节点:node-orbit/node-parent(前缀node-+ 用途。TransformNode不是网格,Inspector 里它排在网格之间,用mesh-会误导)材质:mat-box/mat-sphere(前缀mat-+ 关联网格)clone()和createInstance()给的也是新对象的名字,同样带前缀:第 11 章的sourceTree.clone()产出mesh-tree-1、mesh-tree-2,它们在 Inspector 的场景树里和源网格并排站着,不写前缀就分不出哪个是原本体。这三条规矩的价值在Inspector 里显现:当你打开 Inspector 的场景树,一眼就能看出哪个节点是什么角色。如果都用默认名(box、sphere、material0),场景一大就分不清谁是谁。本书约定的范围:下面两类代码里,显式命名的对象必须带类型前缀——每章末尾的完整模板代码。它会被读者整段复制走,也直接对应在线演示,名字必须能自己说明自己。同一个场景里出现两台以上同类对象的示例。这时名字的唯一作用就是分辨,不写前缀就会认错人。正文里那些只演示单个 API 的小片段——场景里就一个立方体、一盏灯——允许用裸名(box、material),因为那里没有需要分辨的东西。记法一句话:**名字的价值在于分辨;场景里只有一个,就不用分辨。**匿名对象(如临时计算的 Vector3)仍然不用命名。相机名再补一条细则,免得各章各起一套:一个场景里只有一台相机→ 一律camera-main,不加用途后缀。本书绝大多数章节属于这种。一个场景里同时存在两台以上→ 主相机仍叫camera-main,其余按用途命名:camera-top(俯视)、camera-ortho(正交副视图)、camera-minimap(小地图)、camera-rtt(渲进 RenderTargetTexture 的那台)、camera-secondary(备用交互机位)。唯一的例外是第 20 章:那一章每个示例都是一个独立场景、里面只有一台相机,但"它是哪一种相机"本身就是教学内容,所以名字标出族别(camera-arc/camera-follow)——你在 Inspector 里要先认出的正是这个。为什么这样切:维护成本落在读上而不是写上。单相机章统一成camera-main,你从任何一章复制模板都不用改名;而一旦画面里真有多台相机,靠下标(scene.cameras[1])或同名去指代迟早认错人——getCameraByName()遇到重名只返回先注册的那台,Inspector 场景树里两行同名更是无从下手。打开 Inspector 的场景树标签,你应该看到这样的层级:Scene ├─ camera-main (ArcRotateCamera) ├─ light-hemi (HemisphericLight) ├─ light-dir (DirectionalLight) ├─ mesh-box (Mesh) │ └─ mat-box (StandardMaterial) └─ mesh-sphere (Mesh) └─ mat-sphere (StandardMaterial)每个节点的类型括号里标明了类名,点击mesh-box右侧属性栏会显示它的position/rotation/scaling、包围盒尺寸、材质引用等。如果命名不带前缀,当场景里有 20+ 个对象时,你很难一眼看出哪个是相机、哪个是灯光——这就是前缀的价值。5.3.3 dispose 的级联效应scene.dispose()不是"只销毁场景本身",它会级联清理:所有 mesh(调用各自的dispose())所有 material所有 light所有 camera所有 texture(如果没被其他场景共享)这意味着你不需要手动遍历scene.meshes逐个 dispose——那是多余且危险的(重复释放会报错)。唯一需要你手动清理的是引擎管不到的东西:setInterval/setTimeout(第 1 章坑 1-07)addEventListener(第 23 章)自定义 Observable 的订阅者(第 2 章坑 2-02)React/Vue 组件的订阅(第 45 章)这条纪律写成一句话:Scene 内的归 Scene,Scene 外的归你自己。5.3.4 为什么不用全局变量老教程里常见这种写法:letscene:Sceneletcamera:ArcRotateCameraletbox:Meshfunctioninit(){scene=newScene(engine)camera=newArcRotateCamera(...)box=MeshBuilder.CreateBox(...)}这在单页面 demo 里能跑,但在本书的实验台架构下有三个致命问题:切章节时不会清理:scene是全局的,切换到 ch06 时 ch05 的场景还在内存里(因为全局变量还引用着它)。GC 无法回收,造成内存泄漏。命名冲突:如果 ch05 和 ch07 都定义了let box: Mesh,TypeScript 会报重复声明;即使用不同名字,Inspector 里也会混乱。无法并行测试:你想同时打开两个标签页对比 ch05 和 ch06 的效果,全局变量会让两者互相干扰。本书的create()模式把一切限制在函数作用域内:exportasyncfunctioncreate({engine}:ChapterArgs):PromiseChapterHandle{constscene=newScene(engine)// 局部变量constcamera=newArcRotateCamera(...)// 局部变量// ...return{scene,dispose(){scene.dispose()}}}dispose()被调用后,这些局部变量失去引用,GC 可以回收。这就是模块化隔离的价值。5.4 常见"白屏/黑屏"排查表新手写场景最常遇到的两类问题:“白屏”(canvas 全白,无报错)和"黑屏"(canvas 全黑,无报错)。它们的原因高度集中,下面是排查顺序:现象第一怀疑检查步骤控制台报No camera defined没有激活相机:scene.activeCamera是undefined先确认相机真的new出来了(构造函数默认就把它设成激活相机);只有建相机时传了第 7 个参数false,或想切到另一台相机,才需要手动scene.activeCamera = camera。attachControl()不会帮你激活,它只负责输入白屏,无报错相机位置/朝向不对在 Inspector 里看相机位置;或临时加camera.alpha = 0; camera.beta = Math.PI / 2重置视角黑屏,无报错没灯光 + 材质需要光照加一盏HemisphericLight;或把材质改成StandardMaterial并设emissiveColor(自发光)黑屏,但有模型轮廓灯光强度为 0 或方向背对检查light.intensity 0;用 Inspector 看灯光方向向量模型只显示一半背面剔除(backface culling)创建网格时设sideOrientation: Mesh.DOUBLESIDE(第 9 章)模型太大/太小看不见相机距离不合适临时设camera.radius = 10拉远;或在 Inspector 里看网格包围盒尺寸材质是纯白/纯黑贴图加载失败或通道未设Network 面板看 404;Inspector 材质面板看贴图是否挂上切章节后上一章节的东西还在上一章节的dispose()没清干净确认调用了scene.dispose();查第 2 章坑 2-02(Observable 泄漏)模型闪烁或 z-fighting两个网格在同一深度把一个稍微移开(mesh.position.z += 0.01);或启用 polygon offset(第 16 章)阴影缺失或锯齿严重ShadowGenerator 分辨率太低Inspector 里看 shadow map 尺寸;提高到 2048×2048(第 19 章)透明物体渲染顺序错误默认渲染队列按距离排序设material.needAlphaBlending = true并用renderingGroupId控制顺序(第 16 章)移动端触控无响应attachControl()未绑定或 touch-action 被 CSS 拦截确认camera.attachControl()已调用;CSS 加touch-action: none(第 43 章)这张表覆盖了你前 20 章会遇到的 90% 画面问题。遇到不在表里的情况,第一反应永远是:打开 Inspector,看场景树和 Stats 面板。5.4.1 从 Playground 搬代码到本地的五步法Babylon 官方 Playground(https://playground.babylonjs.com/)是最快的 API 试验场。但 Playground 代码不能直接复制到本地——它用了一些简化写法。下面是标准搬运流程:步 1:在 Playground 里验证效果。找到你要用的 API 示例,确认它在浏览器里跑得通。步 2:复制核心逻辑,去掉 PG 特有语法。Playground 常用var、全局engine/scene、createScene()函数。替换为:var→const/let全局engine→ 从ChapterArgs解构createScene()→create({ engine, ... }: ChapterArgs)步 3:补上 import 语句。Playground 不写 import,因为它用 UMD 全局包。本地必须写:import{Scene,ArcRotateCamera,...}from'@babylonjs/core'步 4:添加 meta 和 dispose。本章模板里的meta对象和dispose()函数是实验台契约必需的,Playground 没有。步 5:本地运行并对照 Inspector。确保画面一致,然后用 Inspector 检查场景树是否符合命名规范。这个流程在第 9–11 章会反复用到,因为 MeshBuilder 的很多变体参数直接在文档里看不直观,Playground 试最快。5.5 本书的工程约定为了让你在第 45 章封装引擎时不至于面对一团乱麻,本书从一开始就执行以下约定:5.5.1 文件命名章节代码:src/chapters/chNN-slug.ts(NN是两位章号,slug是短横线分隔的英文描述)手稿:手稿/chNN-标题.md(中文标题,与章节名一致)工具脚本:tools/name.mjs(.mjs表示 ES Module)5.5.2 变量命名场景对象:带类型前缀(camera-*/light-*/mesh-*/mat-*/node-*)临时计算值:小驼峰(finalPosition/averageDelta)常量:大写下划线(MAX_INSTANCES/DEFAULT_COLOR)5.5.3 dispose 纪律每章的create()必须返回{ scene, dispose }。dispose()里必须清理:自定义 Observable 订阅者、定时器、DOM 元素。不要在dispose()里调用engine.dispose()(Engine 由 hub 管理)。5.5.4 Observable 解绑规则add()的返回值必须接住:const observer = observable.add(cb)。dispose()里必须observable.remove(observer)。如果回调只触发一次,用add(cb, undefined, undefined, undefined, true)(最后一个参数unregisterOnFirstCall)。这条规则在第 2 章坑 2-02 已经提过,这里重申是因为它是本书最隐蔽的性能杀手——不报错、不崩溃,只是越来越卡。5.5.5 注释纪律本书对注释的要求很明确:只写"为什么",不写"做什么"。// ✗ 坏注释:复述代码box.position.y=0.5// 把盒子 y 坐标设为 0.5// ✓ 好注释:解释动机box.position.y=0.5// 抬高半个单位,让盒子底面贴地(地面在 y=0)理由很简单:代码本身已经说了"做什么"(position.y = 0.5),注释的价值在于解释"为什么选 0.5 而不是 0 或 1"。这类信息在三个月后你回头看代码时才有用。例外情况:复杂算法的步骤说明可以写多行注释,但必须控制在 5 行以内。超过 5 行的逻辑应该拆成独立函数,用函数名表达意图。这条纪律贯穿全书,违反它会在代码审查时被标记。5.7 场景装配的三种模式根据场景复杂度,本书区分三种装配模式。理解它们的适用边界,能帮你避免"用大炮打蚊子"或"小马拉大车"。模式 A:单场景静态装配(本章模板)特点:一个 Scene,固定数量的网格/灯光/相机所有对象在create()里一次性建好dispose()时全部销毁适用:第 4–19 章的所有示例、产品配置器的单个产品页。优点:简单直观,Inspector 里一目了然。缺点:场景大了之后create()函数超过 100 行,难以维护。模式 B:多场景切换(第 33 章资产加载)特点:多个 Scene 实例,按需创建和销毁用AssetsManager或SceneLoader异步加载外部资源每个场景有自己的 dispose 逻辑适用:数字孪生看板(不同园区对应不同场景)、游戏关卡切换。优点:内存隔离好,切场景时旧场景完全释放。缺点:切换时有短暂黑屏(除非做预加载),代码复杂度翻倍。模式 C:单场景动态增删(第 31 章物理交互)特点:一个长期存在的 Scene运行时动态创建/销毁网格(如玩家拾取物品、子弹发射)用对象池(object pool)复用网格,避免频繁 GC适用:WebXR 展厅、第三人称游戏、粒子系统。优点:无切换黑屏,交互流畅。缺点:需要手动管理内存(对象池),dispose 纪律更严格。本书前 19 章用模式 A,20–37 章过渡到模式 C,38–45 章引入模式 B 的变体。你现在只需要掌握模式 A,但心里要有个底:这套模板不是唯一答案,而是起点。5.7.1 完整搬运示例:从 PG #ABC123 到本地假设你在 Playground 看到这个示例(伪代码):// Playground 代码varcreateScene=function(){varscene=newBABYLON.Scene(engine);varcamera=newBABYLON.ArcRotateCamera("camera",-Math.PI/2,Math.PI/2.5,6,BABYLON.Vector3.Zero(),scene);camera.attachControl(canvas,true);varlight=newBABYLON.HemisphericLight("light",newBABYLON.Vector3(0,1,0),scene);varbox=BABYLON.MeshBuilder.CreateBox("box",{size:1},scene);returnscene;};按五步法搬运:步 1:验证效果。在 PG 里运行,确认蓝色方块能旋转查看。步 2:替换语法。var→constBABYLON.前缀去掉(改用 import)canvas参数去掉(v9 attachControl 无参)createScene()→create({ engine }: ChapterArgs)步 3:补 import。import{Scene,ArcRotateCamera,Vector3,HemisphericLight,MeshBuilder}from'@babylonjs/core'步 4:加 meta 和 dispose。exportconstmeta={id:'05',title:'...',goal:'...'}asconstexportasyncfunctioncreate({engine}:ChapterArgs):PromiseChapterHandle{constscene=newScene(engine)// ... 核心逻辑 ...return{scene,dispose(){scene.dispose()}}}步 5:本地运行对照。访问?ch=05,用 Inspector 确认相机/灯光/网格命名一致。最终代码就是 5.3 节的完整模板。这个流程在第 9–11 章会反复用到,因为 MeshBuilder 的很多参数(如tube的路径数组、lathe的轮廓点)在文档里看不直观,PG 试最快。5.6 预期输出与自查运行本章代码(npm run dev后访问?ch=05),你应该看到:你看到含义蓝色方块 + 橙色球体四件套装配成功鼠标拖拽能旋转视角ArcRotateCamera控制正常Inspector 场景树里有camera-main/light-hemi/mesh-box等命名规范生效页面下方显示"相机:camera-main / 灯光数:2 / 网格数:2 / 材质数:2"describe()通道正常切换到别的章节再切回来,画面干净无残留dispose()级联清理生效如果任何一项不符合,回到 5.4 排查表逐项核对。5.7 本章的坑5-01|先建网格后加相机,render 时报错。虽然 Babylon 允许延迟设相机,但runRenderLoop一旦启动就会每帧调用scene.render(),此时没相机就抛异常。→相机必须在第一次 render 之前存在。5-02|scene.dispose()后还访问scene.meshes。dispose 后的场景处于未定义状态,访问其属性可能返回空数组或报错。→ dispose 后立即把scene变量置null或不再引用。5-03|材质改了但画面不变。StandardMaterial的属性修改后会自动标记 dirty,但某些高级属性(如customShaderNameResolve)需要手动material.markAsDirty(Material.AllDirtyFlag)——markAsDirty(flag: number)的实参不能省(material.pure.d.ts:902)。→ 第 13 章详述。5-04|命名不带前缀,Inspector 里分不清谁是谁。这不是 bug,但会在第 8 章场景图变大时让你痛苦。→ 从现在开始养成习惯。5.8 章节挑战把本章模板里的ArcRotateCamera换成UniversalCamera,并手动设camera.position和camera.rotation,使画面看起来和第 5 章截图一致。(提示:UniversalCamera继承自TargetCamera,有setTarget(),可以直接camera.setTarget(Vector3.Zero());如果想练手动算朝向,就改设camera.rotation。)故意违反装配顺序:先建网格,再加相机。观察报错信息,并解释为什么runRenderLoop启动时机决定了相机必须提前存在。在dispose()里注释掉scene.dispose(),切换到别的章节再切回来,用 Inspector 确认上一章节的网格/材质是否还在内存里。(答案:还在,造成内存泄漏。)给本章场景加第三个网格和一个新材质,并确保 Inspector 里能看到它们的命名符合本书约定。思考题:为什么scene.dispose()不能放在create()函数的末尾?(提示:return { scene }之后调用者还要用它渲染;如果在 create 内部就 dispose,返回的是一个已销毁的空壳。这是新手常犯的时序错误。)5.9 本章小结场景装配的正确顺序是:相机 → 灯光 → 网格 → 材质,违背它可能导致隐性 bug。ArcRotateCamera是本书的默认调试相机,自带轨道控制且attachControl()支持无参调用。命名带类型前缀(camera-/light-/mesh-/mat-/node-),Inspector 里一目了然。scene.dispose()级联清理其下的所有对象,但你仍需手动清理引擎管不到的东西(定时器、addEventListener、自定义 Observable)。白屏/黑屏的 90% 原因集中在相机、灯光、材质三处,用 Inspector 能快速定位。Observable 解绑纪律:add 接住返回值,dispose 里 remove,否则慢性泄漏。5.10 术语卡术语英文一句话场景装配scene assembly按顺序把相机/灯光/网格/材质装进场景级联清理cascading disposalscene.dispose()自动清理其子对象背面剔除backface cullingGPU 不渲染背对相机的三角形以节省算力自发光emissive材质不依赖灯光也能亮的颜色通道默认材质default material网格未指定材质时引擎自动分配的灰色 StandardMaterial轨道相机arc rotate camera绕目标点旋转的相机,适合调试

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑