Orillusion

    • 注册
    • 登录
    • 搜索
    • 版块
    • 最新
    • 标签

    orillusion入门系列二 | 快速入门

    中文社区
    orillusion引擎 engine
    2
    2
    396
    正在加载更多帖子
    • 从旧到新
    • 从新到旧
    • 最多赞同
    回复
    • 在新帖中回复
    登录后回复
    此主题已被删除。只有拥有主题管理权限的用户可以查看。
    • O
      oldguy 最后由 shuangliu 编辑

      前文我们自己动手使用引擎体验到了3D的效果,今天我们尝试梳理一下使用引擎做3D开发从何处入手,快速的从整体上了解如何使用 orillusion 开发3D项目。

      引擎的挑战

      3D是一项面向视觉的技术,为了获得更逼真、更炫酷、更流畅的体验,需要极强的理论根基和工程实践经验。建议先粗略的看看引擎所要面对的一些实际问题,聊一下对引擎浮浅的认识,以便我们能更好的理解如何使用引擎。

      • 从贵族到平民 数学是构建3D世界的支柱,3D效果的发展离不开对数学和算法的深刻理解和应用,比如为了体现光线的真实感,为了实现服装飘逸的感觉,都有许多个数学公式作为底层支撑。另一方面需要充分了解硬件的特性,这都是非常耗费时间的工作,引擎使得没有这方面基础的普通开发者集中时间在实现需求上。
      • 从贪婪到克制 无论硬件是否会继续以遵循摩尔定律的规律提升,引擎要以足够贪婪的程度榨取硬件的性能,使硬件上的投资获得回报,同时要克制自身对资源的占用,即要又要的典范。
      • 从混乱到秩序 引擎所面向的世界是多方面的,不同的硬件之间如何协调,多变的网络环境,多种资源处理,稳定灵活高效的任务调度等等,在引擎的帮助下有条不紊的组织在一起。
      • 始终面带微笑 引擎要发挥出足够的作用,必须要让用户足够容易上手,提供给用户要有足够友好的api,还有完备的工具链和文档,所以引擎要提供尽可能的友好使用体验。

      使用引擎

      因为以上罗列的一些原因,引擎需要需要做许多取舍,在使用引擎的时候也有一些固定的要求。大体上我们分为搭建环境、引擎配置、引擎初始化、开启渲染任务这几个步骤。

      搭建环境

      Orillusion 支持多种方式安装,具体可以参考官方文档,上一篇文章使用了 vue&vite 系列的开发环境,这里假设您已经进行了安装。

      引擎配置

      引擎为了满足多样的需求和不同的环境,提供了许多配置选项,一般代码的最初位置进行引擎配置,后续的初始化和运行需要依赖配置项。这里只列出两条配置示例,更多的配置项可以参考官方文档。

      // 最大实体数量
      Engine3D.engineSetting.memorySetting.doMatrixMaxCount = 100000;
      // 最大灯光数量
      Engine3D.engineSetting.lightSetting.maxLight = 1024;
      

      初始化

      引擎进行了配置之后,需要进行初始化,在初始化这一步可以根据需求申请必要的内存。这里我们可以配置 canvas变量,也可以指定渲染前回调、每一帧回调以前每一帧渲染后的回调。

      // 先配置,后初始化
      await Engine3D.init({
          canvasConfig:{
              // 配置 cavans
              canvas: document.getElementById("webGpuCanvas"),
              alpha: false,
              zIndex: 1
          },
          beforeRender: ()=>{
              // 每一帧渲染前回调
          },
          renderLoop: ()=>{
              // 每一帧回调
          },
          lateRender: ()=>{
              // 每一帧渲染后回调
          }
      });
      

      我们目前没有特别的功能需求,一般用最简化的初始化代码。

      await Engine3D.init();
      

      功能代码

      编写一个相对简单的3D程序,我们可以简单的将过程理解为几个步骤:

      1. 开辟一个3D空间:创建一个场景,相当于一个3D空间,后续的资源可以添加到这个场景中。
      // 创建一个场景
      let scene3D: Scene3D = new Scene3D();
      
      1. 设置一个观察点:这个观察点我们一般用相机组件来体现,所有看到的场景内的信息都是通过相机组件的角度实现的,相机支持一系列的配置参数和方法,后续我们会有专门的章节来学习,同时该相机也要添加至场景中。
      // 创建一个实体对象,用来管理相机组件
      let cameraObj: Object3D = new Object3D();
      // 向 相机 对象内添加相机组件 Camera3D 是我们的相机组件
      let camera = cameraObj.addComponent(Camera3D);
      
      // 相机的配置略
      ...
      
      // 添加相机至场景
      scene3D.addChild(cameraObj);
      
      1. 添加物体:这里我们为了演示,添加了一个立方体,同样需要添加至场景中。
      // 创建一个对象
      const obj = new Object3D();
      // 创建渲染组件
      let mr = obj.addComponent(MeshRenderer);
      // 设置形状
      mr.geometry = new BoxGeometry(5, 5, 5);
      // 设置材质
      mr.material = new HDRLitMaterial();
      // 添加到场景
      scene3D.addChild(obj);
      

      开始渲染任务

      所谓渲染形象来说是,将场景内的景物,通过相机作为观察点,生成二维图像,显示在演示设备上的过程。我们这里一般显示设备是前端的canvas,所以渲染可以理解为从场景到canvas的过程,为了保持流畅,需要高速的刷动作。
      渲染方式有多种,最常见的是前向渲染,其它模式我们遇到再聊。

      // 创建一个前向渲染器
      let renderJob = new ForwardRenderJob(this.scene);
      // 后处理
      // ...
      // 开始渲染任务
      Engine3D.startRender(renderJob);
      

      后处理

      从代码上看,后处理是要放到开始渲染任务之前,但是从逻辑上这里放到最后一步,因为后处理是在生成图片后,界面显示前所做的一次纯粹的图片处理。
      后处理需要在开始渲染任务前添加在渲染器中。

      renderJob.addPost(new SSRPost());//屏幕空间反射
      renderJob.addPost(new SSAOPost());//屏幕空间SSAO
      renderJob.addPost(new GlobalFog());//屏幕空间雾化
      renderJob.addPost(new HDRBloomPost());//HDR Bloom 泛光
      renderJob.addPost(new FXAAPost());//屏幕抗锯齿
      

      后处理是个相对独立又非常有趣的功能,后续我们会专门深入了解。

      调测工具

      为了更好的观察程序的运行状态和动态的操控功能,了解两款相对应的工具很有必要。

      性能监控(Stats)

      Orillusion 官方提供的一个性能监控工具组件,可以显示引擎的运行状态,我们可能直接添加到场景中即可,默认会显示在运行界面的左上角,显示位置可以修改,这里我们使用默认设置就可以了。

      import {Stats} from '@orillusion/core'
      scene.addComponent(Stats);
      

      界面操控(dat.gui)

      一段程序在完成时,逻辑已经通过代码固定下来了,若需动态的改变程序逻辑,脱离不开设计好的输入控制和代码本身的逻辑。但是出于调试或测试的原因,需要动态的修改某个api或变量如何做呢?我们假设只通过界面来控制逻辑,orillusion 官方提供了 GUIHelp 工具类来实现这个功能,不过我们当前推荐使用 dat.gui 来做界面操控工具。

      简介

      dat.gui 是由google工程师出品的很轻量级界面操控工具,使用 javascript 实现,可以方便的集成至基于web的系统内,实现灵活的界面操控,特别适合作为3D程序观察调试程序的工具。
      dat.gui的优点显而易见:

      • 使用简单 dat.gui可以自动的操作dom节点,同步至界面显示效果,不需要我们手动繁琐的通过js来操作dom结构。
      • 轻量级 dat.gui代码量不大,不会对系统的加载造成太大的负担。
      • 功能强大 dat.gui提供了数字、逻辑、颜色、字符串、函数等几种数据类型,不同的数据类型有与之相适配的界面元素,为修改代码的变量和逻辑提供了很大的便利。

      用法

      详细的dat.gui建议查看官方或其它资料,我们这里只是简单的介绍需要用到的部分,后续随着操控部分的增加也会不断的更新,现在我们仅需要熟悉事件监听即可。
      目前支持两种事件监听类型:

      事件 触发
      onChange 值被修改时触发
      onFinishChange 失去焦点时触发

      具体代码在后续代码中一共演示。

      编写代码

      1. 添加@别名 以便代码中可以使用 @ 指代源码路径
        a. 安装node类型 npm i @types/node
        b. 修改 vite.config.ts 文件,新加 @ 别名配置
      export default defineConfig({
        resolve: {
          alias: {
            '@': join(__dirname, "src"),
          }
        },
        plugins: [vue()]
      })
      

      c. 修改 tsconfig.ts 文件,配置TypeScript类型提示,增加 baseUrl和paths配置项

      "baseUrl": ".",
      "paths": {
        "@/*": ["src/*"]
      }
      
      1. 重构目录结构
        a. 修改 demo 目录为 orilluson
        b. 在 src目录创建 views目录
        c. 在views 目录创建 Index.vue单文件组件,编辑代码如下
      <template>
      
      </template>
      
      <script lang="ts" setup>
        import { onMounted } from 'vue';
        import Hello from '@/orillusion/hello';
        onMounted(()=>{
          new Hello().run();
        })
      </script>
      
      <style scoped>
      </style>
      

      d. 重构项目根目录的App.vue文件,编辑代码如下

      <template>
        <Index></Index>
      </template>
      
      <script lang="ts" setup>
      import Index from "@/views/Index.vue";
      </script>
      
      <style scoped>
      
      </style>
      
      1. 安装 dat.gui: npm install --save dat.gui
      2. 完善 hello.ts ,主要从以下几个方面来进行
        a. 重构 Hello 类,将 函数内的临时变量声明成类成员变量
        b. 根据我们前面的引擎步骤,增加处理函数
        c. 增加 dat.gui 操作立方体的控制项
      3. 完成 hello.ts 代码
      import { Engine3D, Scene3D, Object3D, Camera3D, HoverCameraController, MeshRenderer, BoxGeometry, HDRLitMaterial, ForwardRenderJob, Stats } from "@orillusion/core";
      import * as dat from 'dat.gui';
      
      export default class Hello {
          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.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 renderJob: ForwardRenderJob = new ForwardRenderJob(this.scene);
              // 开始渲染
              Engine3D.startRender(renderJob);
          }
      
          /**
           * 创建立方体
           */
          private async createBox() {
              // 创建一个对象
              this.boxObj = new Object3D();
              // 创建渲染组件
              let mr: MeshRenderer = this.boxObj.addComponent(MeshRenderer);
              // 设置形状
              mr.geometry = new BoxGeometry(5, 5, 5);
              // 设置材质
              mr.material = new HDRLitMaterial();
              // 添加到场景
              this.scene.addChild(this.boxObj);
          }
      
          private async addGUI(){
              // 创建 dat 实例
              const gui = new dat.GUI();
              // 创建保存属性值对象
              const geometryInfo = {
                  x: 0,
                  y: 0,
                  z: 0
              };
      
              // 创建一个x坐标事件监听,当修改x值时,直接修改立方体坐标。
              gui.add(this.boxObj, "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;
              });
          }
      
      }
      

      完成项目

      今天的代码我们主要演示了两款测试工具,在左上角是性能监控面板,在右上角是操控面板,用鼠标滑动滚动条或者直接输入数值,可以分别理性立方体的x、y、z的值,物体对应的会发生移动。
      8414362f-b932-4fa1-97d3-fd5cff4010b2-image.png

      代码解析

      今天的代码在第一篇的基础上有所增加,但是以下三个类型是第二次出现了,我们不去深究更详细的原理,需要对这三个常用的对象类型有所了解。

      • Object3D 是引擎内置的实体对象,通常被当做基本的组件容器,可以通过不同组件组合来实现不同的类型功能。我的理解是,Object3D内置一些基础功能对象,还可以是功能组件的容器,比如我们这里创建一个用于管理立方体的容器boxObj。
      • MeshRenderer 是一个用于渲染网格的组件,这个组件非常重要,包含几何属性和材质属性,每个需要渲染的物体都需要有几何属性和材质属性。
      • HDRLitMaterial PBR 材质,基于物理渲染,旨在模拟现实世界光照效果,以后我们会专门来学习材质的部分,这里只要知道这是一种材质就可以。
        以上三个类型都非常重要,首先我们要创建一个Object3D作为容器,然后在这个容器中创建MeshRenderer作为渲染组件,渲染组件中需要实例化几何体和材质。就这样由容器与功能组件相互配合完成功能,当然都需要按照一定的层级结构添加至场景中。

      小结

      今天根据我的理解总结了一个引擎所必须面对的几个难点,可以更好的理解引擎的设计,同时拆解了 orilluson 代码的几个步骤,以便能够快速的上手3D代码开发。进一步的介绍了两个非常常用的调测工具,为以后自由的调用功能提供了工具。之后继续完善项目,运行项目演示效果。最后我们熟悉了最常见的三个组件。
      作为3D新手,后续会不断的记录学习过程,期待与你一起学习一起飞!

      1 条回复 最后回复 回复 引用 1
      • A
        ashton 最后由 编辑

        请问这个入门系列有 demo 代码没有

        webGPU

        1 条回复 最后回复 回复 引用 0
        • First post
          Last post

        Recent Post

        • 请问有没有本次测试所使用的threejs、babylon和orillusion的源码?

          • 阅读更多
        • B

          哪位大佬知道,纹理闪烁是怎么回事吗?.

          • 阅读更多
        • @StephenChips 同求

          • 阅读更多
        • A

          请问这个入门系列有 demo 代码没有

          • 阅读更多
        • O

          经过几次的学习已经能够构建出一个空间(场景),并在空间中创建物体(几何体),物体可以有不同的外观(材质),与现实的效果足够逼真(光照),终于把最重要的相关性最强的几部分3D功能用起来了。不过面对这块空间想做点什么,又感觉缺少了点什么,是的,只能观看不能操作,如果我要通过键盘、鼠标对场景进行实时的干预该如何做呢,经过了解输入系统可以满足我们的要求。

          输入系统

          输入系统是个比较杂乱的部分,不同平台都有对应的封装,我们可以回忆一下Win32编程将键盘和鼠标的输入集成到了事件系统,用户操作按键或操作鼠标会触发对应的消息码,指示消息,附带参数包含具体的按键信息或鼠标信息,按键信息一般包含按键码或鼠标键位。再回忆一下DOM的事件系统,使用addEventListener将click或mouse类的事件挂载,然后在回调函数中获得结果……
          回忆结束我们可以总结出来几个输入系统的特点:1、挂载感兴趣的事件;2、回调函数得到触发时处理业务逻辑。需要注意的是,键盘需要有按键表进行区分按键,对应的是鼠标需要区分不同按键,以及屏幕坐标,辅助键等一些附属信息。
          出于好奇orilluson的输入系统如何实现的,找来源码进行了一个大体的了解,可以看到输入系统的核心类是InputSystem,该类继承于CEventDispatcher类,CEventDispatcher类是可调度事件的所有类的基类,包含了事件的注册,注销,分发和清理等功能实现。内部保存了监听对象列表,当有消息需要处理时通过遍历监听器列表触发回调函数。InputSystem继承了CEventDispatcher类的事件处理能力外着重实现了键盘鼠标的事件处理。
          具体执行步骤如下:

          Engine3D.init:初始化引擎后,实例化了InputSystem类,并将canvas实例传入InputSystem类; InputSystem.initCanvas:InputSystem监听了画布的键盘与鼠标事件; addEventListener:引擎或对象通过addEventListener函数来挂载用户监听; dispatchEvent:当有挂载的监听事件响应时,回调函数会得执行。
          在输入系统的支持下,可以很轻松的使用键盘和鼠标与触控。
          输入系统的回调事件在类CEvent中,先熟悉一下这个类的常用定义: type:事件类型对应的一个字符串常量; param:注册事件时传递的参数,在注册事件时写入的参数在这里可以读出; ctrlKey:事件发生时 Ctrl 是否被按下,通过查询该键的值来判断Ctrl键的状态; altKey:事件发生时 Alt 是否被按下,通过查询该键的值来判断Alt键的状态; shiftKey:事件发生时 Shift 是否被按下,通过查询该键的值来判断Shift键的状态; 关于坐标

          一直以来的学习路径是以实用为主,但是现在必须要接触一点点不能称之为理论的理论了,那就是坐标系统。

          世界坐标

          首先要解决一个困惑的地方,过去在3D空间中的所有坐标都可以称为世界坐标,世界坐标是三维的,有三个维度(x,y,z),一般在引擎中创建可以由系统使用,开发用户程序需要遵守引擎对于世界的规划,相当于场景作为一个空间,世界坐标是对这个空间制定的规则。这里歪个楼,骇客帝国之所以叫矩阵,是不是因为在3D引擎中对空间世界的处理也是以矩阵为基础的。再拉回来,世界坐标一般以(0,0,0)为中心,我们创建的物体默认的位置也是在这里的,这里是世界的中心,一般分为右手或左手坐标系,好了关于世界坐标系这里已经够用了。

          屏幕坐标

          说回到屏幕坐标是我们过去所熟悉的,首先屏幕坐标是一个二维坐标,以像素为单位,屏幕的左下角为起点,向屏幕的左侧和上方依次是x和y坐标的正向。在网页开发中我们通过DOM事件系统获得的当前坐标一般都是指的屏幕坐标。在网页开发中并不是绝对的没有z轴,CSS中的z-index属性是否可以理解成一种z轴坐标呢。

          相互转换

          屏幕坐标是我们最终渲染到屏的最终展现形式,世界坐标是在三维空间内的标识,两者经常需要相互转换,例如今天需要讨论的输入系统的使用。假设在屏幕上点击了一个位置,需要转换到世界坐标,相似的在世界坐标内的位置或距离也需要转换为屏幕坐标。
          坐标转换有标准的算法,这里我们不必如此费力,完全可以借助引擎的工具,经过一翻查找,在相机组件的实现类Camera3D,有坐标转换的工具,可以一起熟悉一下

          object3DToScreenRay:世界坐标转换屏幕坐标; ScreenRayToObject3D:屏幕坐标转换为世界坐标; 键盘输入

          使用键盘输入,首先需要熟悉两种键盘事件:

          KEY_DOWN:键盘按下事件,使用输入系统挂载该事件,将会得到按下键盘事件通知; KEY_UP:键盘弹起事件,使用输入系统挂载该事件,将会得到弹起键盘事件通知;
          下面来一起梳理一下使用流程: 初始化:必要的引擎初始化; 输入挂载:使用键盘挂载系统指定事件和回调; 处理回调:在回调中获取参数。 基础示例

          这里写了一个最基本的示例,只将键盘的事件打印了出来。

          import { Engine3D, Scene3D, Object3D, Camera3D, HoverCameraController, ForwardRenderJob, DirectLight, KeyEvent } from "@orillusion/core"; export default class Keyboard { 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(); // 创建一个场景 this.scene = new Scene3D(); // 创建一个相机 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); } /** * 引擎功能代码 */ private async setup() { Engine3D.inputSystem.addEventListener(KeyEvent.KEY_UP, this.keyUp, this); Engine3D.inputSystem.addEventListener(KeyEvent.KEY_DOWN, this.keyDown, this); } /** * 启动渲染 */ private async start() { // 创建前向渲染 let renderJob: ForwardRenderJob = new ForwardRenderJob(this.scene); // 开始渲染 Engine3D.startRender(renderJob); } private keyDown(e: KeyEvent) { console.log('keyDown:', e.keyCode, e); } private keyUp(e: KeyEvent) { console.log('keyUp:', e.keyCode, e); } }

          运行这个示例后,在场景中按下或弹起键盘,在控制台能够看到输出。

          KeyEvent

          在回调函数中获得的参数类型是KeyEvent,KeyEvent是CEvent的子类,除了CEvent类的参数外,对于键盘事件的使用主要在于对该类型的解析,这里需要详细的了解事件的参数细节,常用到的需要进行一个了解:

          keyCode:按键code值,枚举类型可以参考官方文档的KeyCode定义。 鼠标与触控

          电脑端的鼠标操作与移动端的触控操作有许多共同的地方,在具体用法时如果能够合并为一,是可以节省一半的事件挂载操作的,不过需要留意触控与鼠标的事件对应关系。
          有了前面键盘操作的基础,鼠标与触控使用类型,我们先看支持的事件类型:

          POINTER_CLICK:触摸点击事件,对应鼠标的单击事件; POINTER_MOVE:触摸滑动事件,对应鼠标的移动事件 POINTER_DOWN:触摸开始事件, POINTER_UP:触摸结束事件 POINTER_OUT:触摸滑出事件
          既然已经合并了,后面鼠标与触控用触控来说明吧。 基础示例

          先实现一个最基础的触控功能,与键盘类似,先注册事件,然后响应事件。

          import { Engine3D, Scene3D, Object3D, Camera3D, HoverCameraController, ForwardRenderJob, PointerEvent3D } from "@orillusion/core"; export default class Mouse { cameraObj: Object3D; camera: Camera3D; scene: Scene3D; async run() { await this.init(); await this.setup(); await this.start(); } /*** * 配置并初始化引擎 */ private async init() { // 初始化引擎 await Engine3D.init(); // 创建一个场景 this.scene = new Scene3D(); // 创建一个相机 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); } /** * 引擎功能代码 */ private async setup() { Engine3D.inputSystem.addEventListener(PointerEvent3D.POINTER_UP, this.onUp, this); Engine3D.inputSystem.addEventListener(PointerEvent3D.POINTER_DOWN, this.onDown, this); Engine3D.inputSystem.addEventListener(PointerEvent3D.POINTER_CLICK, this.onPick, this); Engine3D.inputSystem.addEventListener(PointerEvent3D.POINTER_OVER, this.onOver, this); Engine3D.inputSystem.addEventListener(PointerEvent3D.POINTER_OUT, this.onOut, this); Engine3D.inputSystem.addEventListener(PointerEvent3D.POINTER_MOVE, this.onMove, this); } /** * 启动渲染 */ private async start() { // 创建前向渲染 let renderJob: ForwardRenderJob = new ForwardRenderJob(this.scene); // 开始渲染 Engine3D.startRender(renderJob); } private onUp(e: PointerEvent3D) { console.log('onUp:',e); } private onDown(e: PointerEvent3D) { console.log('onDown:',e); } private onPick(e: PointerEvent3D) { console.log('onPick:',e); } private onOver(e: PointerEvent3D) { console.log('onOver:',e); } private onOut(e: PointerEvent3D) { console.log('onOut:',e); } private onMove(e: PointerEvent3D) { console.log('onMove:',e); } } PointerEvent3D

          触控的参数是以PointerEvent3D类型作为回调函数的参数传递到应用,PointerEvent3D是CEvent的子类,除了CEvent类的参数外,需要熟悉一下这个类型的关键字段。

          mouseX:当前鼠标所在位置的X坐标; mouseY:当前鼠标所在位置的Y坐标; movementX:当前事件和上一个鼠标事件之间鼠标在水平方向上的移动值; movementY:当前事件和上一个鼠标事件之间鼠标在垂直方向上的移动值;
          坐标系列的数值请注意,可以使用前面相机组件提供的转换函数进行转换,不必自己写算法进行转换。 由对象挂载

          前面的挂载直接由引擎的输入系统挂载,这样在整个场景中都会响应,如果只需要在一个物体中响应鼠标的事件,我们可以将事件挂在物体上,为什么可以这么做呢,找出来代码可以看到,物体的容器是Object3D类,而Object3D类是Entiry的子类,Entity的父类是CEventDispatcher类,正是因为Object3D通过CEventDispatcher,继承了事件的能力。这一套继承加组件式的结构,实在是太好用了,有没有。
          这样就有了以下的代码:

          // 创建一个对象 this.boxObj = new Object3D(); this.boxObj.localPosition = new Vector3(0,0,0); // 创建渲染组件 let mr: MeshRenderer = this.boxObj.addComponent(MeshRenderer); // 设置形状 mr.geometry = new BoxGeometry(5, 5, 5); // 设置材质 mr.material = new HDRLitMaterial(); // 添加到场景 this.scene.addChild(this.boxObj); boxObj.addEventListener(PointerEvent3D.PICK_CLICK, this.onClick, this); onClick(e: PointerEvent3D) { console.log('onPick:',e); }

          运行后可以在控制台看到输出

          小结

          经过前面几次的学习,已经能够完事的构建出一个空间了,但是这块空间仍然缺乏灵动的能力,不能随时响应我们的操控,输入系统是一个随时干预系统的大杀器,可以让我们获得掌控感,是否控制欲获得了满足。
          今天只是一些最基础的用法,发挥想象力可以使这个空间好玩起来了。
          作为3D新手,后续会不断的记录学习过程,期待与你一起学习一起飞!

          • 阅读更多

        Copyright © 2022 Orillusion | Contact US