orillusion入门系列三 | 几何体
-
前两次的学习总结中,成功运行了一个最基础的功能,对引擎的的整体代码进行了归纳,根据我的习惯提供了一个并不严谨的开发步骤,并且介绍了两个工具。主要解决了认识引擎以及如何开始使用引擎。今天我准备学习使用引擎开发真正的功能了,在这里继续使用大白话(水平有限只能如此)介绍学习过程。
专业的 3D 系统开发需要符合软件工程实践,只以像我这样的小白视角,入门为目的提供一个思路,我们前面了解到首先要通过场景开辟一个空间,加上相机开启上帝视角,那么之后呢?我认为是“造物”,一个3D空间无论复杂还是简单,都可以看成是一个虚拟世界,是对真实世界的理解的反映,在真实世界中我们能看到的物体是由点、线、面组成的,在3D场景中最基础的是由点组成的,3D中的点由三个坐标(x、y、z)分量构成的,GPU一般是按照三角型处理的,描绘点我们一般称为顶点。在3D中的一座山、一条路、一个篮球,都是由若干的顶点构成的。但是只有顶点是不够的,每个物体还会有不同的外观,我们一般用材质来表示,材质对物体的外观进行装饰成不同的样式。所以顶点+材质就组成了一个物体,这个物体我们一般称为网格(mesh),今天我们先忽略材质,专注于造物。几何体
面向3D的世界我还是学前班阶段,所以我们从最基础的几何体开始。orillusion目前提供了四种几何体可以直接使用,并不算多,实践中肯定会需要更多的类型吧,期待后续会增加,这里先熟悉这四种,对于了解几何体是足够的了。
长方体
长方体是我的最爱,我们在第一天就熟悉了如何使用长方体,一个长方体由宽度、高度、深度三个分量组成,这里我们复习一下如何使用
// 创建一个容器对象 let boxObj: Object3D = new Object3D(); // 创建渲染组件 let mr: MeshRenderer = boxObj.addComponent(MeshRenderer); // 创建一个立方体 mr.geometry = new BoxGeometry(5, 5, 5); // 设置材质 mr.material = new LitMaterial(); // 添加到场景 scene.addChild(boxObj);
这里需要注意,几何体的属性在创建时通过构造函数参数指定,不支持动态的修改,动态改变形状建议使用Object3D对象的缩放(scale)属性。球体
球体也是我们非常熟悉的一种几何体,创建球体必须要指定半径,以及水平和垂直分段数。
// 创建一个对象 let boxObj: Object3D = new Object3D(); // 创建渲染组件 let mr: MeshRenderer = boxObj.addComponent(MeshRenderer); // 设置球体的实例 mr.geometry = new SphereGeometry(3, 100, 100); // 设置材质 mr.material = new LitMaterial(); // 添加到场景 scene.addChild(boxObj);
运行效果如下
平面
创建一个平面我们至少需要指定长和宽两个属性
// 创建一个对象 let boxObj: Object3D = new Object3D(); // 创建渲染组件 let mr: MeshRenderer = boxObj.addComponent(MeshRenderer); // 设置形状 mr.geometry = new PlaneGeometry(10, 10); // 设置材质 mr.material = new LitMaterial(); // 添加到场景 scene.addChild(boxObj);
运行效果如下
圆柱体
我们这里创建一个半径为5,高为10的圆柱体
// 创建一个对象 let boxObj:Object3D = new Object3D(); // 创建渲染组件 let mr: MeshRenderer = boxObj.addComponent(MeshRenderer); // 设置形状 mr.geometry = new CylinderGeometry(5, 5, 10); // 设置材质 mr.material = new LitMaterial(); // 添加到场景 scene.addChild(boxObj);
运行效果如下
Object3D 对象
我们不是第一次接触Object3D对象了,这里我们通过实例用一下这个组件,我们以长方体为例,长方体是通过组件的方式添加到一个Object3D容器中的,所以通过Object3D可以操作立方体的属性。
位置
分别可以直接读写对象的x、y、z坐标。
boxObj.x = v; boxObj.y = v; boxObj.z = v;
也可以通过 Vector3 类型一次性设置三个分量,这里不作演示了
旋转
分别可以直接读写对象x、y、z三个方向的旋转角度。
this.boxObj.rotationX = v; this.boxObj.rotationy = v; this.boxObj.rotationz = v;
也可以通过 Vector3 类型一次性设置三个分量,这里不作演示了
缩放
分别可以直接读写对象x、y、z三个方向的缩放。
boxObj.scaleX = v; boxObj.scaleY = v; boxObj.scaleZ = v;
也可以通过 Vector3 类型一次性设置三个分量,这里不作演示了
动态设置开关
设置对象的 enable 属性,可以对该对象以前所有子对象以前脚本的有效性进行设置
boxObj.enable= v;
动态显示或隐藏
设置对象的 visible 属性可以对对象显示或隐藏
boxObj.visible = v;
使用脚本
前面创建的物体看起来很单调,用鼠标操作一下才会动,如何让这个物体本身有一定的行为呢。组件一般用来封装可复用的功能,添加至不同的Object3D中,引擎内部会自动调用该组件的功能作用在这个容器对象中。
这里我们看一个最基本的组件脚本的模板:class Script extends ComponentBase { // 覆写 初始化 public init(){ // 该函数在组件被创建时调用,可以用来初始化内部的变量 // 注意,此时组件被挂载到 Object3D 上,所以无法访问 this.object3D } // 覆写 渲染开始 public start(){ // 该函数在组件开始渲染前被调用, // 一般访问 this.object3D, 通常用来获取节点的属性或其他组件 } // 覆写 onUpdate public onUpdate() { // 每帧渲染循环调用,通常定义节点的循环逻辑 // 例如,每一帧更新物体旋转角度 this.object3D.rotationY += 1; } }
以上模板的注释说明了用法,这里我准备做一个自动旋转的功能,只要实现 update 函数就可以做到。
update(): void { // 旋转 this.transform.rotationX = Math.sin(Time.time * 0.001) * 100.0; }
之后将该组件添加至boxObj中,看来需要熟识上面的脚本模板,这几个生命周期函数非常重要,在后面定制功能时必不可少。而且除了用户代码,引擎内部也定义了一些组件,后面我们要不断去了解。
完成代码
这里以立方体为例,将代码罗列一下。
import { Engine3D, Scene3D, Object3D, Camera3D, HoverCameraController, MeshRenderer, BoxGeometry, LitMaterial, View3D, AtmosphericComponent } from "@orillusion/core"; import * as dat from 'dat.gui'; import { Stats } from "@orillusion/stats"; import { RotationScript } from "./rotationScript"; export default class Geometry { cameraObj: Object3D; camera: Camera3D; scene: Scene3D; boxObj: Object3D; async run() { await this.init(); await this.setup(); await this.start(); } /*** * 配置并初始化引擎 */ private async init() { // 初始化引擎 await Engine3D.init(); } /** * 引擎功能代码 */ private async setup() { // 创建一个场景 this.scene = new Scene3D(); // 添加性能监控面板 this.scene.addComponent(Stats); this.scene.addComponent(AtmosphericComponent); // 创建一个相机 this.cameraObj = new Object3D(); this.camera = this.cameraObj.addComponent(Camera3D); // 设置相机类型 this.camera.perspective(60, window.innerWidth / window.innerHeight, 1, 5000.0); // 设置相机控制器 let controller = this.cameraObj.addComponent(HoverCameraController); controller.setCamera(20, -20, 25); // 添加相机至场景 this.scene.addChild(this.cameraObj); this.createBox(); this.addGUI(); } /** * 启动渲染 */ private async start() { let view = new View3D(); // 指定渲染的场景 view.scene = this.scene; // 指定使用的相机 view.camera = this.camera; // 开始渲染 Engine3D.startRenderView(view); } /** * 创建立方体 */ private async createBox() { // 创建一个对象 this.boxObj = new Object3D(); // 创建渲染组件 let mr: MeshRenderer = this.boxObj.addComponent(MeshRenderer); // 设置形状 mr.geometry = new BoxGeometry(5, 5, 5); // 设置材质 mr.material = new LitMaterial(); // 添加到场景 this.scene.addChild(this.boxObj); } private async addGUI(){ // 创建 dat 实例 const gui = new dat.GUI(); // 创建保存属性值对象 const geometryInfo = { enable:true, visible:true, rotationX:0, rotationY:0, rotationZ:0, scaleX:0, scaleY:0, scaleZ:0, x: 0, y: 0, z: 0, }; // 创建一个x坐标事件监听,当修改x值时,直接修改立方体坐标。 gui.add(geometryInfo, "enable").onChange((v) => { console.log('enable:', v); this.boxObj.enable = v; }); gui.add(geometryInfo, "visible").onChange((v) => { console.log('visible:', v,this.boxObj); this.boxObj.visible = v; }); gui.add(geometryInfo, "x", -100, 100).step(0.1).onChange((v) => { console.log('x:', v); this.boxObj.x = v; }); gui.add(geometryInfo, "y", -100, 100).step(0.1).onChange((v) => { console.log('y:', v); this.boxObj.y = v; }); gui.add(geometryInfo, "z", -100, 100).step(0.1).onChange((v) => { console.log('z:', v); this.boxObj.z = v; }); gui.add(geometryInfo, "rotationX", -100, 100).step(0.1).onChange((v) => { console.log('rotationX:', v); this.boxObj.rotationX = v; }); gui.add(geometryInfo, "rotationY", -100, 100).step(0.1).onChange((v) => { console.log('rotationY:', v); this.boxObj.rotationY = v; }); gui.add(geometryInfo, "rotationZ", -100, 100).step(0.1).onChange((v) => { console.log('rotationZ:', v); this.boxObj.rotationZ = v; }); gui.add(geometryInfo, "scaleX", 0, 10).step(0.1).onChange((v) => { console.log('scaleX:', v); this.boxObj.scaleX = v; }); gui.add(geometryInfo, "scaleY", 0, 10).step(0.1).onChange((v) => { console.log('scaleY:', v); this.boxObj.scaleY = v; }); gui.add(geometryInfo, "scaleZ", 0, 10).step(0.1).onChange((v) => { console.log('scaleZ:', v); this.boxObj.scaleZ = v; }); } }
脚本完整代码
import { ComponentBase, Time } from "@orillusion/core"; export class RotationScript extends ComponentBase { onUpdate(): void { // 旋转 this.transform.rotationX = Math.sin(Time.time * 0.001) * 100.0; } }
脚本不停的更新帧数据,运行时能够看到这个方立体在自动旋转。右侧操作面板对应的操作也可以看到变化。
小结
今天学习了最基本的功能,没有按照通常的3D学习顺序,这里只按照最朴素的习惯性思维来介绍。现在我们学会了造物,后续要继续学习如何装扮这些物体。
作为3D新手,后续会不断的记录学习过程,期待与你一起学习一起飞!