
本篇第 1 篇 · 走进 3D 配套示例examples/ch02/index.html本章目标能说出 scene、camera、renderer 各自负责什么并解释renderer.render(scene, camera)为什么必须同时带两个参数。记住 three.js 的空间约定右手坐标系、y 轴朝上能用AxesHelper读懂一个场景里东西在哪、朝哪放。不看参考能默写出三要素样板代码含setSize和setPixelRatio两行容易被漏掉的设置。上一章你看见了画面但没有看懂代码。这一章把examples/ch01那 20 行拆成零件同时建立 three.js 世界里最重要的一块地基——坐标系。后面第 2 篇的数学基础全部从它出发。2.1 一个比喻记住三要素把渲染一帧画面想象成拍一张照片代码里的对象片场里它是职责边界THREE.Scene拍摄现场地块一个容器所有要被拍到的东西都必须摆进这个现场THREE.Mesh现场里的道具一个具体物体 形状geometry 外观配方materialTHREE.PerspectiveCamera摄影机决定从哪看、朝哪看、看多宽它不渲染任何东西THREE.WebGLRenderer摄影师 冲洗设备把现场 机位算成一张图像写到canvas上注意职责边界这栏的三个措辞它们是后面很多 bug 的诊断入口物体不add进 scene 就等于没摆进片场——摄影师拍不到camera 只管取景不管合成renderer 是唯一的输出端一个 Scene 可以喂给多个 renderer比如做画中画、缩略图一个 camera 也可以在不同时刻拍不同 scene。2.2 坐标系这个世界的门牌号3D 场景里一切位置都是门牌号必须先约定怎么读门牌。three.js 的约定是右手坐标系y 轴朝上默认单位为1 1 米单位不是强制的是社区惯例见 Ch1 实验 1。y上 │ │ │ │________ x右 ╱ ╱ z朝向屏幕外的你用右手判定四指从 x 轴弯向 y 轴拇指指向的就是 z 轴。three.js 里**z 指向屏幕外、朝向观众**摄像机默认放在 z 一侧往原点看。这一点与许多软件不同换工具时要心里有数软件/标准上方向备注three.js / OpenGLy 朝上-z 为相机视线方向本书世界Blender / 游戏引擎常见z 朝上导出模型进 three.js 要做轴向转换第 19 章网页 CSS / 图像坐标y 朝下所以贴图 UV 的 v 轴方向经常反第 8 章怎么亲眼确认用AxesHelper——在场景里放一个画面角上会出现红绿蓝三根短线红x绿y蓝zRGB 顺序对应 XYZ。配套的还有GridHelper地面网格和CameraHelper把相机视锥画出来。它们只用于开发调试正式发布前删掉。本章示例里这三个都有下一章开始我们就不用了——但任何时候画面不对先摆一个 AxesHelper 看方向这是 3D 开发的第一诊断动作。2.3 逐行拆解那 20 行代码按执行顺序分四组。每组末尾有现在只需/第 N 章深讲的标注——这是本书对复杂度的显式管理。组一现场constscenenewTHREE.Scene();Scene 就是一个容器对象本身不可见、不产生任何像素。它还顺手管着两样全局设定scene.background背景可赋颜色或贴图和scene.fog雾效让远处物体逐渐融进背景色。本章示例用到了这两个。——fog 与背景贴图第 15 章见。组二机位一个相机需要的全部位置 朝向constcameranewTHREE.PerspectiveCamera(45,innerWidth/innerHeight,0.1,100);camera.position.set(3,2,4);camera.lookAt(0,0,0);PerspectiveCamera构造函数的四个参数逐个对应摄影机怎么架fov视场角 45镜头纵向张开多少度。越小越望远画面被拉近、空间感压缩越大越广角夸张的近大远小、鱼眼边缘畸变。人眼舒适区间大约 45~75。aspect宽高比 内宽/内高画面宽高比必须等于 canvas 的实际宽高比否则圆球渲染成椭圆——这是常见坑 2 的前半段窗口变化时还要同步更新示例里做了resize监听。near近裁剪面 0.1比这更近的物体不画。它必须大于 0且相机离物体比 near 还近时物体会消失常见坑 1 的另一半。far远裁剪面 100比这更远的不画。near 与 far 之间围成一个梯形取景空间叫视锥frustum——流水线第 ① 站之后、第 ② 站剔除时GPU 真的在拿视锥逐个三角形做检查。position.set(3, 2, 4)与lookAt(0, 0, 0)架机器要拧两颗螺丝——摆在哪位置、镜头朝哪朝向。相机和所有物体一样生活在 2.2 节的坐标系里。少了lookAt相机默认只朝着自己的 -z 方向画面十有八九是空的。为什么透视相机参数这么重要因为 fov/near/far 三个数最终会被做成一个 4×4 投影矩阵第 11 章你把它完整推导出来现在先把参数含义用熟。组三道具constcubenewTHREE.Mesh(newTHREE.BoxGeometry(1,1,1),newTHREE.MeshNormalMaterial());scene.add(cube);Mesh 网格 形状 外观两个零件各管一半BoxGeometry是一个 1×1×1 米盒子的三角形清单MeshNormalMaterial是用面朝向涂色的配方。new完不scene.add等于道具造好了没搬进片场——画面空白新手第一大坑第 7、8 章把 geometry 和 material 拆开深讲。组四摄影师constrenderernewTHREE.WebGLRenderer({canvas:document.getElementById(stage),antialias:true});renderer.setSize(innerWidth,innerHeight);renderer.setPixelRatio(Math.min(devicePixelRatio,2));renderer.setClearColor(#11151c);构造参数里的canvas告诉 renderer 往哪个canvas元素上画。不传它会自己造一个 canvas 塞进 body布局会乱。setSize设置 canvas 的绘制尺寸回忆 Ch1 坑 3尺寸只听它的。setPixelRatio高分屏Retina、现代手机一个 CSS 像素对应 2~3 个物理像素不设会糊用Math.min(devicePixelRatio, 2)封顶是行业惯例——再高的像素比带来的清晰度收益抵不上 GPU 开销第 20 章算这笔账。antialias: true打开抗锯齿三角形边缘不再呈台阶状。快门renderer.render(scene,camera);一条命令把流水线走一遍从 camera 的视点把 scene 算成 canvas 上的一张图。必须同时有两个参数——现场和机位缺一不可同一个 scene 换机位就是另一张照片。上一章setAnimationLoop里每帧执行一次它就是每秒按 60 次快门。2.4 最小可运行示例把坐标系摆进画面examples/ch02/index.html在本章知识点上做了三件事地面网格、坐标轴、三块颜色不同且摆在坐标正方向上的板子每个对象一眼对应一条轴scene.add(newTHREE.AxesHelper(5));// 红x 绿y 蓝z长5米constgridnewTHREE.GridHelper(10,10);// 10米见方、10格的xOz地面网格scene.add(grid);constplane(color)newTHREE.Mesh(newTHREE.PlaneGeometry(1,1),newTHREE.MeshBasicMaterial({color,side:THREE.DoubleSide}));constredOnXplane(red);redOnX.position.set(1.5,0.5,0);constgreenOnYplane(lime);greenOnY.position.set(0,1.5,0);greenOnY.rotation.yMath.PI/2;constblueOnZplane(dodgerblue);blueOnZ.position.set(0,0.5,1.5);scene.add(redOnX,greenOnY,blueOnZ);运行它对着画面做一件事说出绿色板子为什么被rotation.y π/2转了一下。答案PlaneGeometry默认朝 z它的法线是 z 轴不转的话我们从 -z 一侧的相机看它是一条边绕 y 轴转 90° 后朝向 x 轴方向才能被看到正面。——这就是 2.2 节坐标系 面有朝向的第一次配合。DoubleSide是双保险正面背面都画第 8 章再谈要不要用。2.5 改造实验删掉scene.add(cube)本章示例则删greenOnY那块东西消失其余照旧。确认容器决定画什么。camera.position.set(0, 0.5, 0.05)保持lookAt(0,0,0)几乎怼在原点。结果一片空——相机进入了 near 裁剪面以内所有物体都太近被裁掉了。这是坑 2 的实体化。fov改成 100退后一点再对比感受广角的边缘拉伸改成 15远处的东西黏到近处物体上空间压缩。这个手感在第 11 章推导完投影矩阵后会有理论解释。把浏览器窗口从宽拉成窄观察画面示例里resize监听器会同步camera.aspect。把监听器删掉再试窗口变化时圆变椭圆、板子变形——aspect 错了相机不知道它只会用上一次构造时的比例画。2.6 常见坑坑 1新new的物体没add进 scene。画面空白或少了某样东西第一反应永远是检查 add。坑 2相机太靠近物体或 near 设得太大。现象是看进去全是黑/背景色。相机不是无限穿透的它有一个贴脸的最近取景距离 near。排查思路把 near 改到 0.01 试试或把相机退远。坑 3窗口尺寸变了只改setSize、不改camera.aspect。画面拉伸、比例失真。正确样板已在 ch02 示例内抄走即用window.addEventListener(resize,(){camera.aspectinnerWidth/innerHeight;camera.updateProjectionMatrix();// 必须调参数改完不会自动生效renderer.setSize(innerWidth,innerHeight);});坑 4误以为 camera 是玩家的视角。camera 只是取景框与你在场景里走到哪无关——移动相机 ≠ 移动角色。第一人称、第三人称是谁跟着谁的关系第 23 章游戏实战展开。2.7 练习默写题不看书写出三要素初始化 一次render的完整代码一个红立方体、相机在 (2,2,5) 看原点、背景深灰。写完再对照——重点检查setSize那行和scene.add有没有漏。预测题camera.position.set(0, 0, 10)lookAt(0, 0, 0)一个位于 (3, 2, 4) 的 1×1 立方体会出现在画面哪个方位为什么提示相机在 z 看原点相机自己的 x 轴、y 轴各朝哪动手题给示例加上scene.fog new THREE.Fog(#11151c, 10, 50)把相机挪到(20, 10, 20)观察远处板子发生了什么再挪回来。说出 fog 的两个数字参数分别是什么距离。参考答案略——自查点PerspectiveCamera四参数、Mesh两参数、scene.add(mesh)、renderer.setSize。画面偏右上。相机在 (0,0,10) 看向原点时视线方向是 -z此时它的右方恰好是世界 x、上方是 y对照 ch01 示例的默认机位即可印证。所以 x3 出现在画面右侧y2 出现在上方z4 让它离相机更近一些、显得更大。这道题的要点相机在 z 一侧朝 -z 看时屏幕左右和世界 x并不反号只有当你把相机移到 z 轴负侧往回看时左右才会互换——画面摆位时先想清楚相机视线方向根源就在这一层。fog 两个参数是开始距离 10、完全遮蔽距离 50距离相机 10 米内不受雾影响50 米外融成背景色。相机拉远后板子逐渐消失是因为它们相对相机距离进了雾区间——fog 的参照是相机距离不是世界坐标。本章小结三要素分工scene 是容器不被 add 就不存在camera 是取景框位置朝向两颗螺丝renderer 是唯一输出端尺寸只归setSize/setPixelRatio管。render(scene, camera)每次调用 整条图形管线走一遍动画就是反复按快门。世界是右手坐标系、y 朝上AxesHelper红x绿y蓝z是第一诊断工具。PlaneGeometry默认朝 z物体看不见很多时候是朝向问题——朝向由面的法线决定法线是向量向量是下一章的主角。从 Ch3 起进入第 2 篇数学基础。第一章就说过这些数学不是选读本章你已经两次碰到它的影子朝向/法线、相机参数背后的投影矩阵。下一站向量。本章完整源码本章示例的全部源码都在下面逐字复制即可重建不需要另外下载。每个代码块都是examples/里对应文件的原样副本由脚本生成并逐字节对账所以你抄下来的就是作者这边跑通过的那一份。把下面这个文件逐字存成examples/ch02/index.html然后在examples/的上一级目录里起一个静态服务器python-mhttp.server8000再访问http://127.0.0.1:8000/examples/ch02/。three 由 importmap 从 unpkg 取要联网版本号保持 0.186.1换版本先回看 Ch1 的版本纪律。文件清单ch02/index.html (76 行)ch02/index.html!DOCTYPEhtmlhtmllangzh-CNheadmetacharsetUTF-8metanameviewportcontentwidthdevice-width, initial-scale1.0titleCh2 · 三要素与坐标系/titlestylehtml, body{margin:0;height:100%;overflow:hidden;background:#11151c;}#stage{display:block;}/stylescripttypeimportmap{imports:{three:https://unpkg.com/three0.186.1/build/three.module.js,three/addons/:https://unpkg.com/three0.186.1/examples/jsm/}}/script/headbodycanvasidstage/canvasscripttypemoduleimport*asTHREEfromthree;// ── 组一现场 ──constscenenewTHREE.Scene();scene.backgroundnewTHREE.Color(#11151c);// ── 组二机位 ──constcameranewTHREE.PerspectiveCamera(45,innerWidth/innerHeight,0.1,100);camera.position.set(4,3,6);camera.lookAt(0,1,0);// ── 组三道具 ──scene.add(newTHREE.AxesHelper(5));// 红x 绿y 蓝z长 5 米constgridnewTHREE.GridHelper(10,10);// xOz 地面网格10 米见方、10 格scene.add(grid);constplane(color)newTHREE.Mesh(newTHREE.PlaneGeometry(1,1),newTHREE.MeshBasicMaterial({color,side:THREE.DoubleSide}));constredOnXplane(red);redOnX.position.set(1.5,0.5,0);constgreenOnYplane(lime);greenOnY.position.set(0,1.5,0);greenOnY.rotation.yMath.PI/2;constblueOnZplane(dodgerblue);blueOnZ.position.set(0,0.5,1.5);scene.add(redOnX,greenOnY,blueOnZ);// ── 组四摄影师 ──constrenderernewTHREE.WebGLRenderer({canvas:document.getElementById(stage),antialias:true,});renderer.setSize(innerWidth,innerHeight);renderer.setPixelRatio(Math.min(devicePixelRatio,2));// 坑 3 的正确样板aspect 与尺寸同步window.addEventListener(resize,(){camera.aspectinnerWidth/innerHeight;camera.updateProjectionMatrix();renderer.setSize(innerWidth,innerHeight);});renderer.setAnimationLoop((){renderer.render(scene,camera);});/script/body/html
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。