# babylon-learn-byDoc **Repository Path**: marina-37/babylon-learn-byDoc ## Basic Information - **Project Name**: babylon-learn-byDoc - **Description**: 通过babylon官方网站进行学习,创建一些交互式web文件。 Babylon官网:https://www.babylonjs.com/ - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 3 - **Forks**: 1 - **Created**: 2023-12-27 - **Last Updated**: 2025-10-18 ## Categories & Tags **Categories**: Uncategorized **Tags**: JavaScript, BabylonJS ## README --- typora-root-url: ./src --- # BabylonDemo1 #### 介绍 Babylon初体验:官方文档learn学习 Babylon官网:[Babylon.js: Powerful, Beautiful, Simple, Open - Web-Based 3D At Its Best (babylonjs.com)](https://www.babylonjs.com/) Babylon沙盒:[Babylon.js Sandbox - View glTF, glb, obj and babylon files (babylonjs.com)](https://sandbox.babylonjs.com/) BabylonJs GUI编辑器:[Babylon.js Gui Editor (babylonjs.com)](https://gui.babylonjs.com/) #### 软件架构 1. src:工程所需要的资源文件 > doc:文档类文件 > > imgs:照片类文件 > > musics:音频类文件 > > models:模型文件 2. views:制作的babylon界面,包含实际代码 3. Babylon.gui.min.js/earcut.js:引入js文件 #### 项目工具及语言 vscode(liveServer)+JavaScript # 1.Babylon基础知识 ## 1.1 引擎:engine ​ 引擎变量是负责与较低级别的 API(如 WebGL、Audio 等)交互的类。创建 Babylon 场景(将视觉对象呈现到屏幕的上下文)的构造函数需要引擎与这些较低级别的 API 进行通信。 ## 1.2 必要元素:camera,light,scene 使用 Babylon.js 引擎的项目都需要一个添加了摄像机和灯光的场景:camera,light,scene ```js const createScene = () => { const scene = new BABYLON.Scene(engine); const camera = new BABYLON.ArcRotateCamera("camera", -Math.PI / 2, Math.PI / 2.5, 3, new BABYLON.Vector3(0, 0, 0), scene); camera.attachControl(canvas, true); const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene); return scene; } ``` ## 1.3 Babylonjs引入 ```html //1.CDN引入 ``` ```bash //2.使用vite+Babylonjs的方式 npm create vite@latest npm install --save babylonjs //引入 import * as BABYLON from 'babylonjs' ``` 由于初学Babylon,一切以最简单为主,所以本项目将使用CDN方式进行引入。 ## 1.4 Babylon坐标系 Babylon使用左手坐标系:大拇指指向x,食指指向y,中指指向z ![](https://gitee.com/marina-37/babylon-demo1/raw/master/src/doc/leftHand.png)) 一般默认为: > X---width > > y---height > > Z---deepth 但是涉及到旋转之后,垂直方向的距离不一定由y控制 # 2.Babylon基础方法 ## 2.1 创建圆柱体/多边体 ```js const roof = BABYLON.MeshBuilder.CreateCylinder( "roof", { diameter: 3, //直径 height: 2, //y轴高度 tessellation: 3 //圆柱体有n个边n个角 } ); ``` ## 2.2 创建box ### 2.2.1 面编号 *faceUV* 和 Vector4:用它来获取图像的一部分区域,以应用于盒子的某一面。 *faceUV* 阵列中:(此时以屏幕向外为正面) > 盒子box:0 背面、1 正面、2 右侧、3 左侧、4 顶部, 5 底部 > > 圆柱体Cylinder:面 0是底部,面 1 是连接底部和顶部的边,面 2 是顶部。 > > 拉伸多边形ExtrudePolygon:面 0是顶部,面1是拉伸部分depth,面 2是底部。 ### 2.2.2 面与贴图 > 对于整个贴图图像:(0,0)代表左下角,(1,1)代表右上角 > > 对于部分图像:坐标值将是 0 到 1 之间的分数。 4 维向量:(左下角 x、左下角 y、右上角 x、右上角 y) 面与贴图的位置进行匹配: > 正面,1,(0.0,0.0,0.25,1.0) > > 右面,2,(0.25,0,0.5,1.0) > > 背面,0,(0.5,0.0,0.75,1.0) > > 左面,3,(0.75,0,1.0,1.0) ![](https://gitee.com/marina-37/babylon-demo1/raw/master/src/imgs/cubehouse.png) ### 2.2.3 代码 ```js carUV[0] = new BABYLON.Vector4(0, 0.5, 0.38, 1);//原图 carUV[2] = new BABYLON.Vector4(0.38, 1, 0, 0.5);//图像进行翻转 const faceUV = []; faceUV[0] = new BABYLON.Vector4(0.5, 0.0, 0.75, 1.0); //rear face faceUV[1] = new BABYLON.Vector4(0.0, 0.0, 0.25, 1.0); //front face faceUV[2] = new BABYLON.Vector4(0.25, 0, 0.5, 1.0); //right side faceUV[3] = new BABYLON.Vector4(0.75, 0, 1.0, 1.0); //left side const box = BABYLON.MeshBuilder.CreateBox("box", {faceUV: faceUV, wrap: true}); const faceUV = []; faceUV[0] = new BABYLON.Vector4(0.6, 0.0, 1.0, 1.0); //rear face faceUV[1] = new BABYLON.Vector4(0.0, 0.0, 0.4, 1.0); //front face faceUV[2] = new BABYLON.Vector4(0.4, 0, 0.6, 1.0); //right side faceUV[3] = new BABYLON.Vector4(0.4, 0, 0.6, 1.0); //left side const box = BABYLON.MeshBuilder.CreateBox("box", {faceUV: faceUV, wrap: true }); ``` ## 2.3 box的大小与位置 ### 2.3.1 大小设置scaling: ```js // 第一种设置方式:初始导入时设置大小 const box2 = BABYLON.MeshBuilder.CreateBox( "box2", { width: 2, height: 1.5, depth: 3 } ) // 第二种设置方式:导入后再设置大小 const box3 = BABYLON.MeshBuilder.CreateBox("box3", {}); box3.scaling.x = 1.5; box3.scaling.y = 6.5; box3.scaling.z = 2; const box4= BABYLON.MeshBuilder.CreateBox("box4", {}); box4.scaling = new BABYLON.Vector3(2, 1.5, 3); ``` ### 2.3.2 位置设置position: ```js // position属性将网格的中心置于该位置,也是一个具有属性 x、y 和 z 的向量对象 // 第一种设置: box.position = new BABYLON.Vector3(-2, 4.2, 0.1); // 第二种设置: box.position.x = -2; box.position.y = 4.2; box.position.z = 0.1; ``` 注意: ​ position初始设置在物体原点,会与初始地面相交,如果想令物体恰好在地面ground之上,则需要设置`box.position.y=1/2 box.scaling.y`,但是如果物体发生了旋转,则不能如此计算。 ```js // 例子: // 盒子的高度都是 1.5,因此对于每个盒子来说,位置.y = 0.75 恰好在地面上,不会相交。 //中间 const box1 = BABYLON.MeshBuilder.CreateBox("box1", { width: 2, height: 1.5, depth: 3 }); box1.position.y = 0.75; //左 const box2 = BABYLON.MeshBuilder.CreateBox("box2", {}); box2.scaling.x = 2; box2.scaling.y = 1.5; box2.scaling.z = 3; box2.position = new BABYLON.Vector3(-3, 0.75, 0); //右 const box3 = BABYLON.MeshBuilder.CreateBox("box3", {}); box3.scaling = new BABYLON.Vector3(2, 1.5, 3); box3.position.x = 3; box3.position.y = 0.75; box3.position.z = 0; ``` ## 2.4 旋转 ```js //以下两者效果相同: //第一种 box.rotation.y = Math.PI / 4; box.rotation.y = BABYLON.Tools.ToRadians(45); //第二种 const box = BABYLON.MeshBuilder.CreateBox("box", {}); box.position.y = 2.5; box.rotation.y = Math.PI / 3; box.rotation.x = Math.PI / 3; box.rotation.z = Math.PI / 3; ``` ## 2.5 添加纹理 ```js // .StandarMaterial获取标准纹理材质 // .diffuseColor设置纹理颜色 // .diffuseTexture设置纹理贴图 const groundMat = new BABYLON.StandardMaterial("groundMat");//scene参数可选,默认为当前场景 groundMat.diffuseColor = new BABYLON.Color3(0, 1, 0); ground.material = groundMat; //Color3(r,g,b),000为黑色,111为白色,也可以使用如下代替: //注意:Color3使用的是百分比,如:Color3(124/255,205/255,124/255) BABYLON.Color3.Red(); BABYLON.Color3.Green(); BABYLON.Color3.Blue(); BABYLON.Color3.Black(); BABYLON.Color3.White(); BABYLON.Color3.Purple(); BABYLON.Color3.Magenta(); BABYLON.Color3.Yellow(); BABYLON.Color3.Gray(); BABYLON.Color3.Teal(); //Texture()添加相对或绝对 URL const roofMat = new BABYLON.StandardMaterial("roofMat"); roofMat.diffuseTexture = new BABYLON.Texture( "https://assets.babylonjs.com/environments/roof.jpg", scene ); const box1Mat = new BABYLON.StandardMaterial("boxMat"); box1Mat.diffuseTexture = new BABYLON.Texture( "https://www.babylonjs-playground.com/textures/floor.png" ); roof.material = roofMat; box1.material = box1Mat; ``` ## 2.6 组合网格 ```js // .Mesh.MergeMeshes()函数 // 第一个参数:需要组合的物体数组 // 第二个参数:true,将处理原始网格 // 最后一个参数为 true,允许将原始材料单独应用于与原始网格匹配的零件 const combined = BABYLON.Mesh.MergeMeshes(Array_of_Meshes_to_Combine) //如:但是此时整个房子用同一种材料覆盖了,效果不好。 const house = BABYLON.Mesh.MergeMeshes([box, roof]); //如:此时各自材质与各自网格物体匹配 const house = BABYLON.Mesh.MergeMeshes([box, roof], true, false, null, false, true); ``` ## 2.7 复制网格 ```js // .clone() 克隆 // .createInstance() 创建实例 var clonedHouse = house.clone("clonedHouse") var instanceHouse = house.createInstance("instanceHouse") //如: var newInstance = mesh.createInstance("i" + index); newInstance.position.x = index; newInstance.position.z = index; (method) BABYLON.Mesh.createInstance(name: string): BABYLON.InstancedMesh //Creates a new InstancedMesh object from the mesh model. //@paramname — defines the name of the new instance //@returns — a new InstancedMesh ``` ## 2.8 导入模型 ```js //如: BABYLON.SceneLoader.ImportMeshAsync( "", //Empty string loads all meshes "/relative path/", "myFile" ); BABYLON.SceneLoader.ImportMeshAsync( "model1", //Name of the model loads one model "/relative path/", "myFile" ); BABYLON.SceneLoader.ImportMeshAsync( ["model1", "model2"],//Array of model names "/relative path/", "myFile" ); // 导入模型: // 第一个参数:可以是三种类型中的任何一种,具体取决于是要加载所有模型、仅加载一个模型、加载模型列表 // 第二个参数:模型导入路径 // 第三个参数:模型名称 // 第四个参数:导入场景,scene不写默认为当前场景。 BABYLON.SceneLoader.ImportMeshAsync(model_name, folder_path, file_name, scene); //对导入模型进行操作,我们使用Promise 和 then 方法,以 Promise 的结果调用函数。 BABYLON.SceneLoader.ImportMeshAsync("", "/relative path/", "myFile").then((result) => { result.meshes[1].position.x = 20; const myMesh1 = scene.getMeshByName("myMesh_1"); myMesh1.rotation.y = Math.PI / 2; }); //导入.glb文件: BABYLON.SceneLoader.ImportMeshAsync( "", "https://assets.babylonjs.com/meshes/", "village.glb" ); ``` ## 2.9 添加声音 ```js // Load the sound, give it time to load and play it every 3 seconds const bounce = new BABYLON.Sound( "bounce", "F:/VScode/babylonjs/babylon-demo1/src/Fool_For_You.mp4", scene ); setInterval(() => bounce.play(), 3000); ``` ## 2.10 沿路径拉伸形状来创建网格 我们创建形状轮廓,仅使用 x、y 平面中的点来拉伸 vector3 序列。 ```js const lampShape = []; for(let i = 0; i < 20; i++) { lampShape.push( new BABYLON.Vector3(Math.cos(i * Math.PI / 10), Math.sin(i * Math.PI / 10), 0) ); } lampShape.push(lampShape[0]); //close shape ``` 然后,我们再次使用 vector3s 设置拉伸路径。路径不必局限于 x、y 平面,它可以使用完整的 3D 空间进行描述。 ```js const lampPath = []; lampPath.push(new BABYLON.Vector3(0, 0, 0)); lampPath.push(new BABYLON.Vector3(0, 10, 0)); for(let i = 0; i < 20; i++) { lampPath.push(new BABYLON.Vector3( 1 + Math.cos(Math.PI - i * Math.PI / 40), 10 + Math.sin(Math.PI - i * Math.PI / 40), 0) ); } lampPath.push(new BABYLON.Vector3(3, 11, 0)); ``` 最后,我们形成挤压。 ```js const lamp = BABYLON.MeshBuilder.ExtrudeShape( "lamp", { cap: BABYLON.Mesh.CAP_END, shape: lampShape, path: lampPath, scale: 0.5 } ); ``` ```js // .ExtrudeShape() //创建一个挤压形状网格。挤压是一个参数形状,它没有预定义的形状,它的最终形状将取决于输入参数。 (property) ExtrudeShape: ( name: string, options:{ shape: BABYLON.Vector3[]; path: BABYLON.Vector3[]; scale?: number; rotation?: number; closeShape?: boolean; closePath?: boolean; cap?: number; updatable?: boolean; ... 6 more ...; adjustFrame?: boolean; }, scene?: BABYLON.Scene) => BABYLON.Mesh @paramname — defines the name of the mesh @paramoptions — defines the options used to create the mesh @paramscene — defines the hosting scene @returns — the extruded shape mesh @see — https ://doc.babylonjs.com/features/featuresDeepDive/mesh/creation/param @see — https ://doc.babylonjs.com/features/featuresDeepDive/mesh/creation/param#extruded-shapes ``` # 3.Babylon动画 ## 3.1 网格父项 对父项的任何位置、缩放和旋转的使用也将应用于子项,设置子空间的位置是在父空间中完成的,设置子项的旋转和缩放是在子空间的局部空间中完成的。 父--------影响--------->子 子-------不影响------->父 ```js const boxParent = BABYLON.MeshBuilder.CreateBox( "Box", {faceColors:faceColors} ); const boxChild = BABYLON.MeshBuilder.CreateBox( "Box", {size: 0.5, faceColors:faceColors} ); boxChild.setParent(boxParent); ``` ## 3.2 制作多边形(制造汽车) ```js // 制作一个多边形 const outline [ new BABYLON.Vector3(-0.3, 0, -0.1), new BABYLON.Vector3(0.2, 0, -0.1), ... ]; const car = BABYLON.MeshBuilder.ExtrudePolygon("car", {shape: outline, depth: 0.2}); // .ExtrudePolygon()创建多边形 (property) ExtrudePolygon: ( name: string, options: { shape: BABYLON.Vector3[]; holes?: BABYLON.Vector3[][]; depth?: number; faceUV?: BABYLON.Vector4[]; faceColors?: BABYLON.Color4[]; updatable?: boolean; sideOrientation?: number; frontUVs?: BABYLON.Vector4; backUVs?: BABYLON.Vector4; wrap?: boolean; }, scene?: BABYLON.Scene, earcutInjection?: any) => BABYLON.Mesh // 参数介绍 //@paramname — defines the name of the mesh //@paramoptions — defines the options used to create the mesh //@paramoptions.shape //@paramoptions.holes //@paramoptions.depth //@paramoptions.faceUV //@paramoptions.faceColors //@paramoptions.updatable //@paramoptions.sideOrientation //@paramoptions.frontUVs //@paramoptions.backUVs //@paramoptions.wrap //@paramscene — defines the hosting scene //@paramearcutInjection — can be used to inject your own earcut reference //@returns — the polygon mes ``` 注意: extrudePolygon 和 [PolygonMeshBuilder](https://doc.babylonjs.com/divingDeeper/mesh/creation/param/polyMeshBuilder) 都使用切片算法(earcut),Playground 定义了 earcut,但如果您在自己的文件系统上遵循本教程,则需要通过 [CDN](https://unpkg.com/earcut@latest/dist/earcut.min.js) 或 [NPM](https://github.com/mapbox/earcut#install) 下载 earcut 算法。如果您使用的是 TypeScript,则可以将 earcut 算法作为 earcutInjection 参数注入 [extudePolygon 函数](https://doc.babylonjs.com/typedoc/classes/babylon.meshbuilder#extrudepolygon) 本项目使用``导入earcut算法。 如下是右手坐标系绘制出的汽车底部,将`z=-z`即为正确图像,depth控制小车的高度。 ## 3.3 创建动画 ```js // 创建一个动画 const animWheel = new BABYLON.Animation( "wheelAnimation", "rotation.y", 30, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE ); // 设置关键帧数组,只需要插入关键帧,其余帧Babylonjs将会补足 const wheelKeys = []; //At the animation key 0, the value of rotation.y is 0 wheelKeys.push({ frame: 0, value: 0 }); //At the animation key 30, (after 1 sec since animation fps = 30) the value of rotation.y is 2PI for a complete rotation wheelKeys.push({ frame: 30, value: 2 * Math.PI }); //set the keys animWheel.setKeys(wheelKeys); //Link this animation to a wheel wheelRB.animations = []; wheelRB.animations.push(animWheel); //动画播放:场景中wheelRB轮子,第0帧开始动,到30帧结束,循环动画 //注意:如果将开始帧与结束帧相反,则动画将倒着播放 scene.beginAnimation(wheelRB, 0, 30, true); ``` ```js // .Animation()创建动画方法 constructor BABYLON.Animation( name: string, targetProperty: string, framePerSecond: number, dataType: number, loopMode?: number, enableBlending?: boolean ): BABYLON.Animation //Initializes the animation //@paramname — Name of the animation-名称 //@paramtargetProperty — Property to animate-要进行动画处理的属性(任何物体的属性,如position.x) //@paramframePerSecond — The frames per second of the animation -每秒动画帧数 //@paramdataType — The data type of the animation-数值变化的类型(浮点数、三维矢量、二维矢量、颜色) // Animation.ANIMATIONTYPE_COLOR3 // Animation.ANIMATIONTYPE_FLOAT // Animation.ANIMATIONTYPE_MATRIX // Animation.ANIMATIONTYPE_QUATERNION // Animation.ANIMATIONTYPE_VECTOR2/Animation.ANIMATIONTYPE_VECTOR3 //@paramloopMode — The loop mode of the animation-循环模式(相对、循环、常量) // BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE-从初始值重新启动动画 // BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT -在最终值暂停动画 // BABYLON.Animation.ANIMATIONLOOPMODE_RELATIVE -使用键值渐变重复动画递增 // Animation.ANIMATIONLOOPMODE_YOYO -动画在到达末尾时将反转其方向,而不是从头开始重新开始 //@paramenableBlending — Specifies if blending should be enabled //设置关键帧数组 //动画的关键帧保存在对象数组中,每个对象包含以下两个属性: const myKeys = [ { frame: 0, //帧编号 value: 0.5 //用于要更改的属性 }, { frame: 60, value: 1.0 } } //将关键帧数组通过 .setKeys()方法添加到动画中 myAnimation.setKeys(myKeys); //绑定动画 //将动画添加到物体对象的 animations 数组属性中。 myMaterial.animations.push(myAnim) //动画可以添加到引擎可以访问的任何对象上,即使它还没有 animations 数组属性。 //只需为对象声明一个新的数组属性就可以在目标对象上存储动画。 //也适用于画布之外的内容。 myAnimation.animations = []; myAnimation.animations.push(weightAnimation); // .beginAnimation()动画播放方法 (method) BABYLON.Scene.beginAnimation( target: any, from: number, to: number, loop?: boolean, speedRatio?: number, onAnimationEnd?: () => void, animatable?: BABYLON.Animatable, stopCurrent?: boolean, targetMask?: (target: any) => boolean, onAnimationLoop?: () => void, isAdditive?: boolean ): BABYLON.Animatable //Will start the animation sequence of a given target //@paramtarget — defines the target -定义目标对象 //@paramfrom — defines from which frame should animation start-定义帧开始 //@paramto — defines until which frame should animation run.-定义帧结束 //@paramloop — defines if the animation loops-动画是否循环,循环为true //@paramspeedRatio — defines the speed in which to run the animation (1.0 by default)-播放速度,默认1,值越大越快 //@paramonAnimationEnd — defines the function ``` ## 3.4 创建角色动画 ​ 角色一般是在别的建模软件中已经构建好的,或者是已经购买的,我们只需要将其导入到当前场景中,通过`.then()`方法获取对象的网格和骨架属性,然后再进行修改和调整即可。 ### 3.4.1 方法 #### 3.4.1.1 .movePOV() ​ 网格有一个有用的属性*movePOV*,它允许我们相对于网格的视点移动网格。 ```js /* .movePOV(-x,y,-z) * @param -x:向右移动,网格局部空间的-x方向 * @param y:向上移动,网格局部空间的y方向 * @param -z:向右移动,网格局部空间的-z方向 */ mesh.movePOV(0,0,-6) //向屏幕外方向移动6个单位 ``` ​ (局部网格空间其实还是左手坐标系,如下:) ​ ![](https://gitee.com/marina-37/babylon-demo1/raw/master/src/doc/leftHand2.png) #### 3.4.1.2 .CreateLines() ```js // .CreateLines()创造线段 // 线网格被认为是参数形状,因为它没有预定义的原始形状。它的形状由传递的点数组作为输入参数确定。 (property) CreateLines: ( name: string, options: { points: BABYLON.Vector3[]; updatable?: boolean; instance?: BABYLON.LinesMesh; colors?: BABYLON.Color4[]; useVertexAlpha?: boolean; material?: BABYLON.Material; }, scene?: BABYLON.Scene ) => BABYLON.LinesMesh ``` #### 3.4.1.3 .onBeforeRenderObservable.add() ```js // scene.onBeforeRenderObservable.add() // 在渲染场景之前触发的事件(就在动画和物理之后),通过这种方式,可以逐帧更改对象的属性。 scene.onBeforeRenderObservable.add(() => { //code to execute }); ``` #### 3.4.1.4 mesh.rotate() ​ 这是一种旋转方法。此方法将网格绕给定轴旋转给定角度(以弧度为单位)。 ```js //mesh.rotate() mesh.rotate(axis, angle, BABYLON.Space.LOCAL); //@paramaxis — the axis to rotate around-要旋转的轴 //@paramamount — the amount to rotate in radians-以弧度为单位旋转的量 //@paramspace — Space to rotate in (Default: local)-要旋转的空间(默认:local) //@returns — the TransformNode. ``` ### 3.4.2小球环绕动画 ​ 一个简单的例子:一个球体绕等边三角形的周长边缘重复移动。 ​ 为了在每个渲染帧之前生成动画,球体将移动 0.05 的距离。当它行进的距离大于 4 时,球体将转弯,大于 8 时,球体将再次转弯,当大于周长时,球体将重置并重新开始。 ​ 每当达到所需的距离时,就会转弯,并且数组索引指针 p 将增加 1。模运算符 *%* 用于在数组末尾将指针重置为零。为了防止浮点误差累积,每当索引指针重置为 0 时,球体的位置和旋转也会重置。 ```js // 创建一个小球 const sphere = BABYLON.MeshBuilder.CreateSphere( "sphere", { diameter: 0.25 } ); sphere.position = new BABYLON.Vector3(2, 0, 2); // 画出三角形的三条边 const points = []; points.push(new BABYLON.Vector3(2, 0, 2)); points.push(new BABYLON.Vector3(2, 0, -2)); points.push(new BABYLON.Vector3(-2, 0, -2)); points.push(points[0]); //close the triangle; // 画线 BABYLON.MeshBuilder.CreateLines("triangle", { points: points }) // 构造slide,覆盖区域后再进行旋转 const slide = function (turn, dist) { this.turn = turn;//转向 this.dist = dist;//距离 } const track = []; track.push(new slide(Math.PI / 2, 4));//首边长4 track.push(new slide(3 * Math.PI / 4, 8));//在第二边结束时,距离为4 + 4 track.push(new slide(3 * Math.PI / 4, 8 + 4 * Math.sqrt(2)));//这三条边的长度是4 + 4 + 4 *根号(2) let distance = 0;//总长 let step = 0.05;//前进步长 let p = 0;//数组索引指针 // 每一帧渲染之前执行 scene.onBeforeRenderObservable.add(() => { sphere.movePOV(0, 0, step);//向屏幕内行动step=0.05 distance += step; if (distance > track[p].dist) { //以y轴旋转 sphere.rotate(BABYLON.Axis.Y, track[p].turn, BABYLON.Space.LOCAL); p += 1; p %= track.length;//重置指针 if (p === 0) { distance = 0; sphere.position = new BABYLON.Vector3(2, 0, 2); //重置到初始条件 sphere.rotation = BABYLON.Vector3.Zero();//避免误差 } } }); ``` ### 3.4.3 角色环绕村庄动画 与小球环绕动画类似,主要不同在于人物轨迹track不同,还需要调节人物模型的大小以及动作,相关代码如下: ```js //人物活动轨迹 const track = []; track.push(new walk(86, 7)); track.push(new walk(-85, 14.8)); track.push(new walk(-93, 16.5)); track.push(new walk(48, 25.5)); track.push(new walk(-112, 30.5)); track.push(new walk(-72, 33.2)); track.push(new walk(42, 37.5)); track.push(new walk(-98, 45.2)); track.push(new walk(0, 47)); //人物模型调整 dude.scaling = new BABYLON.Vector3(0.01, 0.01, 0.01); dude.position = new BABYLON.Vector3(-6, 0, 0); scene.beginAnimation(result.skeletons[0], 0, 100, true, 1.0); ``` # 4.碰撞 ## 4.1避免碰撞 ​ 查看两个网格是否接触的最简单方法是使用 *intersectsMesh* 方法。 ```js //每个网格都有一个内置的边界框,该框靠近网格的表面,用于检查网格的交集。 //如果mesh1与mesh2重叠,则为真。 mesh1.intersectsMesh(mesh2); ``` ​ 为了演示 *intersectsMesh* 方法,我们将让角色在汽车的停车处来回行走。如果汽车处于“命中”区域而角色不在,则角色停止移动。 ​ 人物*Dude* 只是头部、躯干、腿部和手臂的支架节点,在这种情况下,绑定它的盒子太小而无法有效检测,我们需要使用它的一个子项来检查交叉点。 ​ 关键代码如下: ```js //hitBox创建透明材质 const wireMat = new BABYLON.StandardMaterial("wireMat"); wireMat.wireframe = true; // wireMat.alpha = 0;隐藏透明边框 //hitBox创建 const hitBox = BABYLON.MeshBuilder.CreateBox( "carbox", {width: 0.5, height: 0.6, depth: 1.5} ); hitBox.material = wireMat; hitBox.position.x = 3.1; hitBox.position.y = 0.3; hitBox.position.z = -7; let carReady = false;//车模型未加载进来时,默认值为false ... //导入car模型后 carReady = true; ... //导入人物模型后 if (carReady) { if (!dude.getChildren()[1].intersectsMesh(hitBox) && scene.getMeshByName("car").intersectsMesh(hitBox)) { return;//人物动画在return之后,若碰撞则人物不动 } } ``` # 5.环境搭建 ## 5.1 山丘 ​ 我们想把村庄设置在一个山谷里。可以从网格创建山丘,但是还有另一种方法可以为地面网格添加垂直高度。这是使用高度图实现的,该地图使用灰色阴影来确定地面的高度。白色区域是最高的部分,黑色区域是最低的部分。 ​ 高度图: ​ ### 5.1.1基本高度图 ```js const largeGround = BABYLON.MeshBuilder.CreateGroundFromHeightMap( "largeGround", "../../src/imgs/villageheightmap.png", { width: 150, height: 150, subdivisions: 20,//细分度,将地面分割为40*40=1600个小部分 minHeight: 0,//黑色区域的垂直高度 maxHeight: 10//白色区域的垂直高度 } ); ``` ### 5.1.2带纹理的高度图 ```js // 材质创建 const largeGroundMat = new BABYLON.StandardMaterial("largeGroundMat"); largeGroundMat.diffuseTexture = new BABYLON.Texture( "https://assets.babylonjs.com/environments/valleygrass.png" ); const largeGround = BABYLON.MeshBuilder.CreateGroundFromHeightMap( "largeGround", "../../src/imgs/villageheightmap.png", { width: 150, height: 150, subdivisions: 60, minHeight: 0, maxHeight: 10 } ); largeGround.material = largeGroundMat; ``` ### 5.1.3分层纹理 ```js ... const ground = BABYLON.MeshBuilder.CreateGround("ground", { width: 24, height: 24 }); ... const largeGround = BABYLON.MeshBuilder.CreateGroundFromHeightMap( "largeGround", "../../src/imgs/villageheightmap.png", { width: 150, height: 150, subdivisions: 60, minHeight: 0, maxHeight: 10 } ); ... largeGround.position.y = -0.01;//确保两个接地不会发生冲突并引起闪烁 ``` ### 5.1.4导入村庄 注意导入村庄模型的时候,ground也会与当前界面的ground产生冲突闪烁情况,和分层纹理的道理一样,修改ground的高度。 ```js BABYLON.SceneLoader.ImportMeshAsync("", "../../src/models/", "scene.glb").then((res)=>{ console.log(res); const villageGround=res.meshes[1]; villageGround.position.y=-0.03; }); ``` ## 5.2天空盒 ### 5.2.1静态环境贴图 ​ 我们可以通过将六个合适的图像应用于大型天空盒立方体的内部来模拟[天空](https://doc.babylonjs.com/features/featuresDeepDive/environment/skybox)的外观。 ​ Skybox 图像通常使用 [CubeTexture](https://doc.babylonjs.com/typedoc/classes/babylon.cubetexture) 加载。CubeTexture 的构造函数采用一个基本 URL,并(默认情况下)附加 `_px.jpg`、`_nx.jpg`、`_py.jpg`、`_ny.jpg`、`_pz.jpg` 和`_nz.jpg` 来加载立方体的 +x、-x、+y、-y、+z 和 -z 朝向的两侧。 ​ 尽管名称为“Texture”,但 CubeTexture *只能*与 [StandardMaterial](https://doc.babylonjs.com/typedoc/classes/babylon.standardmaterial) 的 .[reflectionTexture](https://doc.babylonjs.com/typedoc/classes/babylon.standardmaterial#reflectiontexture) 或 .[refractionTexture](https://doc.babylonjs.com/typedoc/classes/babylon.standardmaterial#reflectiontexture) 属性一起使用,*而不能*与 [.diffuseTexture](https://doc.babylonjs.com/typedoc/classes/babylon.standardmaterial#diffusetexture) 等其他属性一起使用。 > 注意: > > ​ 我将6张天空盒图片存在F:\VScode\babylonjs\babylon-demo1\src\imgs\skybox目录下,项目中url设置为`new BABYLON.CubeTexture("../../src/imgs/skybox", scene);`但是报错`GET http://127.0.0.1:5500/src/imgs/skybox_px.jpg 404 (Not Found)`。 > > ​ 我们可以看到报错显示路径少了一层文件夹`skybox`,所以我们得知,CubeTexture函数中url的skybox其实代指了天空盒的6张图片。上述正确写法应该是`new BABYLON.CubeTexture("../../src/imgs/skybox/skybox", scene);` ```js //设置Skybox const skybox = BABYLON.MeshBuilder.CreateBox("skyBox", { size: 150 }, scene); const skyboxMaterial = new BABYLON.StandardMaterial("skyBox", scene); // 设置筛选状态(true为启用筛选,false为禁用)获取筛选状态 skyboxMaterial.backFaceCulling = false; // 定义用于显示反射的纹理。 skyboxMaterial.reflectionTexture = new BABYLON.CubeTexture("../../src/imgs/skybox", scene); // 将 coordinatesMode 设置为SKYBOX_MODE,直接在立方体上绘制纹理,而不是模拟反射。 skyboxMaterial.reflectionTexture.coordinatesMode = BABYLON.Texture.SKYBOX_MODE; skyboxMaterial.diffuseColor = new BABYLON.Color3(0, 0, 0); skyboxMaterial.specularColor = new BABYLON.Color3(0, 0, 0); skybox.material = skyboxMaterial; ``` 此外,我们还设置限制了相机,使其无法移动到地面以下。 ```js camera.upperBetaLimit = Math.PI / 2.2; ``` ## 5.3精灵树 ### 5.3.1精灵管理器 ​ 为了大批量的渲染,并且保持渲染速度,我们可以使用 sprites(精灵)。 ```js const spriteManagerTrees = new BABYLON.SpriteManager( "treesManager", //参数是管理器的名称 "url to tree image",//图像的 url 2000, //最大精灵数 {width: 512, height: 1024}, //指定精灵宽度和高度的对象(图像) scene ); ``` ### 5.3.2图像集合生成动画 ​ 上面的图像由相同大小的单元格框组成,横向 5 个,纵向4 个。 ​ 精灵动画是通过给出要使用的第一个和最后一个单元格来设置的,无论它是否循环 (true) 以及单元格帧之间的时间。上图为例,管理器中设置的宽度和高度是一个单元格的宽度和高度。 ```js const spriteManagerUFO = new BABYLON.SpriteManager( "UFOManager", "https://assets.babylonjs.com/environments/ufo.png", 1, {width: 128, height: 76} ); const ufo = new BABYLON.Sprite("ufo", spriteManagerUFO); ufo.playAnimation(0, 16, true, 125); ufo.position.y = 5; ufo.position.z = 0; ufo.width = 2; ufo.height = 1; ``` ```js // .Sprite("ufo", spriteManagerUFO) //创建一个新的精灵容器 constructor BABYLON.Sprite( name: string, manager: BABYLON.ISpriteManager ): BABYLON.Sprite //@paramname — defines the name //@parammanager — defines the manager-定义的精灵管理器 //ufo.playAnimation(0, 16, true, 125); //精灵的方法:开启一个动画 (method) BABYLON.Sprite.playAnimation(from: number, to: number, loop: boolean, delay: number, onAnimationEnd?: () => void): void //@paramfrom — defines the initial key-开始帧 //@paramto — defines the end key-结束帧 //@paramloop — defines if the animation must loop-是否循环 //@paramdelay — defines the start delay (in ms)-定义启动延迟(以毫秒为单位) //@paramonAnimationEnd — defines a callback to call when animation ends-动画结束时的回调 ``` # 6.粒子 ## 6.1 *CreateLathe* 方法 ```js (property) CreateLathe: ( name: string, options: { shape: BABYLON.Vector3[];//(Vector3[])数组的Vector3,你想要构造的图像的剖面 radius?: number;//(number)半径值 tessellation?: number;//(number)迭代次数 clip?: number; arc?: number; closed?: boolean; updatable?: boolean; sideOrientation?: number;//边方向 frontUVs?: BABYLON.Vector4; backUVs?: BABYLON.Vector4; cap?: number; invertUV?: boolean; }, scene?: BABYLON.Scene ) => BABYLON.Mesh ``` [Lathe | Babylon.js Documentation (babylonjs.com)](https://doc.babylonjs.com/features/featuresDeepDive/mesh/creation/param/lathe) 例如:创建一个喷泉 shape:所需绘制的物体剖面需要在数组中使用 3D 向量的 x 和 y 分量进行描述。 ![](https://gitee.com/marina-37/babylon-demo1/raw/master/src/imgs/profile.png) ```js const fountainProfile = [ new BABYLON.Vector3(0, 0, 0), new BABYLON.Vector3(10, 0, 0), new BABYLON.Vector3(10, 4, 0), new BABYLON.Vector3(8, 4, 0), new BABYLON.Vector3(8, 1, 0), new BABYLON.Vector3(1, 2, 0), new BABYLON.Vector3(1, 15, 0), new BABYLON.Vector3(3, 17, 0) ]; //Create lathe const fountain = BABYLON.MeshBuilder.CreateLathe( "fountain", { shape: fountainProfile, sideOrientation: BABYLON.Mesh.DOUBLESIDE }, scene ); ``` ## 6.2颗粒喷雾 ```js // 创建粒子系统 var particleSystem = new BABYLON.ParticleSystem( "particles", 5000,//同时存活粒子的最大数量 scene ); //每个粒子的材质 particleSystem.particleTexture = new BABYLON.Texture("../../src/imgs/flare.png", scene); //基本发射器区域是围绕指定点的给定尺寸的框 particleSystem.emitter = new BABYLON.Vector3(0, 10, 0); //起始对象,发射器 particleSystem.minEmitBox = new BABYLON.Vector3(-1, 0, 0); //箱体最小尺寸 particleSystem.maxEmitBox = new BABYLON.Vector3(1, 0, 0); //箱体最大尺寸 //每个粒子的颜色 particleSystem.color1 = new BABYLON.Color4(0.7, 0.8, 1.0, 1.0); particleSystem.color2 = new BABYLON.Color4(0.2, 0.5, 1.0, 1.0); particleSystem.colorDead = new BABYLON.Color4(0, 0, 0.2, 0.0); // 粒子颜色color1和color2的混合模式Blend mode : BLENDMODE_ONEONE, or BLENDMODE_STANDARD particleSystem.blendMode = BABYLON.ParticleSystem.BLENDMODE_ONEONE; //每个粒子的尺寸(随机在最大值和最小值之间) particleSystem.minSize = 0.1; particleSystem.maxSize = 0.5; //每个粒子的寿命(随机在最大值和最小值之间) particleSystem.minLifeTime = 2; particleSystem.maxLifeTime = 3.5; // 每秒发射的粒子数 particleSystem.emitRate = 1500; // 设置每个粒子的重力 particleSystem.gravity = new BABYLON.Vector3(0, -9.81, 0); // 每个粒子发射后的方向,有两个方向 particleSystem.direction1 = new BABYLON.Vector3(-2, 8, 2); particleSystem.direction2 = new BABYLON.Vector3(2, 8, -2); // 粒子速度,最大值最小值以及更新速率 particleSystem.minEmitPower = 1; particleSystem.maxEmitPower = 3; particleSystem.updateSpeed = 0.025; //角速度,以弧度为单位 particleSystem.minAngularSpeed = 0; particleSystem.maxAngularSpeed = Math.PI; //开启粒子系统 particleSystem.start(); ``` ## 6.3 switch on事件 ​ 当我们单击喷泉上的屏幕指针时,我们希望它启动。为此,我们向 *onPointerObservable* 添加一个函数来处理在停止和启动之间切换粒子系统的指针向下事件。 ```js // scene.onPointerObservable.add() //每次从渲染画布接收到输入事件时触发的Observable事件,用指定的回调创建一个新的Observer (method) BABYLON.Observable.add( callback: (eventData: BABYLON.PointerInfo, eventState: BABYLON.EventState) => void, mask?: number, insertFirst?: boolean, scope?: any, unregisterOnFirstCall?: boolean ): BABYLON.Observer<...> (+2 overloads) //@paramcallback — the callback that will be executed for that Observer //@parammask — the mask used to filter observers //@paraminsertFirst — if true the callback will be inserted at the first position, hence executed before the others ones. If false (default behavior) the callback will be inserted at the last position, executed after all the others already present. //@paramscope — optional scope for the callback to be called from //@paramunregisterOnFirstCall — defines if the observer as to be unregistered after the next notification //@returns — the new observer created for the callback scene.onPointerObservable.add((pointerInfo) => { switch (pointerInfo.type) { case BABYLON.PointerEventTypes.POINTERDOWN: if(pointerInfo.pickInfo.hit) { pointerDown(pointerInfo.pickInfo.pickedMesh) } break; } }); ``` # 7.灯光 ## 7.1 光源分类 - 平行光: 光线方向固定,均匀照亮场景中所有对象,用于模拟远处的光源,如太阳光。 - 点光源: 点光源从一个点向外发射光线,光线会随着距离的增大而衰减,点光源常用于补光和模拟近处的光源。 - 聚光灯: 聚光灯的光线在一个锥形区域内发散,超出这个区域的光线会渐渐衰减,聚光灯常用于模拟手电筒和其他有光锥的灯光。 ## 7.2 创建光源 ```javascript // 创建平行光光源 const light = new BABYLON.DirectionalLight( "light",//光源名称 new BABYLON.Vector3(0, -1, 0),//光源的方向 scene//光源所在的场景 ) // 创建点光源 const pointLight = new BABYLON.PointLight( "pointLight",//点光源名称 new BABYLON.Vector3(0, 1, 0),//点光源的方向 scene//点光源所在的场景 ) // 创建聚光灯 const spotLight = new BABYLON.SpotLight( "spotLight",//聚光灯名称 new BABYLON.Vector3(4, 2, 0),//聚光灯的位置 new BABYLON.Vector3(-1, -1, 0),//聚光灯的方向 Math.PI / 3, //聚光灯的角度(聚光灯光锥的角度) 4, //聚光灯的衰减值,数值越大衰减得越快 scene//聚光灯所在的场景 ) ``` ## 7.3 设置光源颜色 ```javascript // 设置平行光光源颜色 light.diffuse = new BABYLON.Color3(0, 1, 0) // 设置平行光光源镜面光(高光)的颜色 light.specular = new BABYLON.Color3(0, 1, 1) // 设置平行光光源强度 light.intensity = 0.5 // 设置点光源颜色-RGB pointLight.diffuse = new BABYLON.Color3(1, 0, 0) // 设置点光源镜面光(高光)的颜色 pointLight.specular = new BABYLON.Color3(1, 1, 0) // 设置点光源强度 pointLight.intensity = 0.5 ``` ## 7.4 设置光源阴影 ```js // 创建阴影 // 传入两个参数,第一个参数是阴影贴图的大小,值越大越清晰;第二个参数是光源 var shadowGenerator = new BABYLON.ShadowGenerator(1024, spotLight); // 设置投射阴影的物体-球体 shadowGenerator.addShadowCaster(sphere); // 设置接收投射阴影的物体-地面 ground.receiveShadows = true; ``` # 8.场景添加图形用户界面 ## 8.1 Babylon.js GUI ​ 向场景添加图形用户界面的一种有用方法是 Babylon.js GUI,它可以将图形化界面用于 Babylon.js 场景画布中并成为其中的一部分,而不是被用于HTML 文档。 1. 引入方式:CDN引入 ```js ``` 2. 引入方式:本地引入(便与分享) ```js ## 8.2实现方式 1. 首先,创建一个特殊的纹理,称为 *AdvancedDynamicTexture*,我们需要在其上其上绘制 GUI 元素,这里的GUI将是一个基于全屏的GUI。 ```js const adt = BABYLON.GUI.AdvancedDynamicTexture.CreateFullscreenUI("UI"); ``` 2. 接着,创建容器面板 *panel* 来保存屏幕右下角的其他元素,将其添加到高级动态纹理*AdvancedDynamicTexture*中。 ```js const panel = new BABYLON.GUI.StackPanel(); panel.width = "220px"; panel.top = "-50px"; panel.horizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_RIGHT; panel.verticalAlignment = BABYLON.GUI.Control.VERTICAL_ALIGNMENT_BOTTOM; adt.addControl(panel); ``` 3. 接下来,创建文本块并将其添加到面板中。 ```js const header = new BABYLON.GUI.TextBlock(); header.text = "Night to Day"; header.height = "30px"; header.color = "white"; panel.addControl(header); ``` 4. 接着,创建滑块并将其添加到面板中。 ```js const slider = new BABYLON.GUI.Slider(); slider.minimum = 0; slider.maximum = 1; slider.borderColor = "black"; slider.color = "#AAAAAA"; slider.background = "#white"; slider.value = 1; slider.height = "20px"; slider.width = "200px"; panel.addControl(slider); ``` 5. 最后,我们需要在滑块中添加一个可观察的事件,以更改光强度。 ```js slider.onValueChangedObservable.add((value) => { if (light) { light.intensity = value; } }); ``` 6. 结束 # 9.相机移动 ```js //相机,相对于目标(地球)的位置可以通过三个参数来设置 const camera = new BABYLON.ArcRotateCamera( "name", alpha_angle, //(弧度)是纵向旋转 beta_angle,//弧度)是纬度旋转 radius, //半径是距目标位置的距离 target_position ); camera.attachControl(canvas, true); ``` 相机运行的示意图如下所示: ## 9.1相机附着在角色上 ```js const camera = new BABYLON.ArcRotateCamera( "camera", Math.PI / 2, Math.PI / 2.5, 150, new BABYLON.Vector3(0, 60, 0) ); camera.parent = dude;//dude为人物角色 ``` ## 9.2跟随角色 除了使用parent之外,我们还可以使用 *FollowCamera* 跟踪角色的动作。 给 *FollowCamera* 一个起始位置和一个要跟踪的目标,以及一个从中查看目标的目标位置。 ```js //创建带有名称、起始位置和可选场景参数的 FollowCamera。 const camera = new BABYLON.FollowCamera( "FollowCam", new BABYLON.Vector3(-6, 0, 0), scene ); camera.radius = 1;//目标距离相机的半径 camera.heightOffset = 8;//设定目标:目标中心上方的高度 camera.rotationOffset = 0;//旋转,以弧度为单位,目标中心在 x y 平面内 camera.cameraAcceleration = 0.005//从当前位置移动到目标位置的加速 camera.maxCameraSpeed = 10//加速停止的速度 camera.attachControl(canvas, true); camera.lockedTarget = targetMesh;//设定目标 ```