Orillusion

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

    orillusion入门系列一 | 第一印象

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

      做了很多年的应用开发想要在技术上有些突破,是时候学点真正的技术了,我选择了3D技术,因为接触3D技术之前我似乎是一只二维生物,只能在一个平面上思考问题,但是3D技术难度不小,所以必须找个足够高的肩膀来踩,那就要借助引擎的力量了。经过一翻搜索,orillusion 着实让我感动,满足了我所有的要求,而且还是国产的。

      为什么选择 orillusion

      没有专业的对比,仅凭我个人的好恶盲目来选择,简单从这两个方面来考虑

      Native or Web?

      如果您是专业人士建议详细对比两种类型的发展历史和原理再做出判断,对于我一个3D新手来说,有一个简单粗暴的判断思路,那就是在应用系统上,CS架构仍在某些特定领域仍然存在,但是能够迁移到BS的,几乎都在大规模的迁移到了BS架构上,所以我挺Web渲染架构。

      足够新

      新人就要有新人的觉悟,老牌的各路引擎已经有无数高手浸淫其中多年了,冒然投入进去会被沉重的历史信息淹没,作技术还是要有点野心的新手终有一天会成老鸟,所以要选择最新推出的面向最新技术架构的引擎,那么有多新呢,标准还没正式应用的够新吧,那就是以面向WebGPU为第一图形标准实现功能,至于WebGPU是什么,我的理解是更新、更快、更强,至于具体如何做到,相信以后我们会知道的。

      准备工作

      唯一必须的的准备工作是做好心理建设,不要想一口吃成个胖子,不要过早深究原理,我们的目标是借助引擎的能力来实现3D效果,除此以外还有两件小事需要留意。

      开发环境

      请先安装nodejs,并配置好环境变量,同时假设vue3已经可以正常使用。这里我使用vite作为项目创建工具,使用vscode做编辑器,没有其它的前置条件,轻装上阵。如果这一步有什么疑问,请自行查找资料,相信聪明的你不会被难住。

      运行环境

      由于WebGPU标准并未正式推出,需要使用开发浏览器,这里推荐 chrome canary ,下载后在在canary中运行效果,由于canary仍然在变动中,需要在地址栏中输入 chrome://flags/#enable-unsafe-webgpu,然后开启 Unsafe WebGPU 选项。

      c7ae164b-15fd-46fe-b723-41491446e63c-image.png
      如果无法开启或者没有效果,需要搜索最新的开启方法,相信不久的将来在正式版就可以附带WebGPU支持了,不需要这么繁琐了。
      同时为了做两手准备,也可以考虑用 Edge Canary 来应个急,下载后正常安装,启动后在地址栏中输入edge://flags/#enable-unsafe-webgpu,开启WebGPU支持。

      创建项目

      这里我们先创建一个普通的vue3项目,再将引擎引入项目,操练起来吧,很快就能看到我们在做什么了。

      1. 创建根目录
        a. 选择一个文件夹,创建orillusion目录
        b. 进入orillusion,并在该目录下打开命令提示行
      2. 创建项目
        a. 执行命令 npm create vite@latest
        b. Project name 下输入 hello3d 作为项目名并回车
        c. 使用键盘方向键选择 vue 作为框架,并回车
        d. 仍然使用键盘方向键选择 vue-ts 作为项目类型,并回车,已经创建了 hello3d 项目目录结构
        e. 输入 cd hello3d 进入项目目录
        f. 输入 npm install 安装依赖包,至此,基本的vue3项目已经创建完成
      3. 安装 orillusion
        a. 输入 npm install @orillusion/core --save 安装 orillusion 开发包(如果安装失败,请注意分辨网络原因或安装权限)
        b. 输入 npm run dev ,可以看到在5173端口启动了服务
        c. 打开 canary 浏览器,输入完整地址可以看到显示了vue3的欢迎页,至此项目已经安装完成,后面进行开发。

      编写代码

      1. 在 vscode 中打开 hello3d 目录
      2. 在 src 目录下新建目录 demo
      3. 在 demo 目录下创建文件 hello.ts
      import { Engine3D, Scene3D, Object3D, Camera3D, ForwardRenderJob, HDRLitMaterial, BoxGeometry, MeshRenderer, DirectLight, HoverCameraController, Color, Vector3 } from "@orillusion/core";
      
      export default class Hello {
          async run() {
              console.log('hello 3d');
              // 初始化引擎
              await Engine3D.init();
              // 创建一个场景
              let scene3D: Scene3D = new Scene3D();
              // 创建一个相机
              let cameraObj: Object3D = new Object3D();
              let camera = cameraObj.addComponent(Camera3D);
              // 设置相机类型
              camera.perspective(60, window.innerWidth / window.innerHeight, 1, 5000.0);
              // 设置相机控制器
              let controller = cameraObj.addComponent(HoverCameraController);
              controller.setCamera(20, -20, 25);
              // 添加相机至场景
              scene3D.addChild(cameraObj);
              // 创建一个对象
              const obj: Object3D = new Object3D();
              // 创建渲染组件
              let mr: MeshRenderer = obj.addComponent(MeshRenderer);
              // 设置形状
              mr.geometry = new BoxGeometry(5, 5, 5);
              // 设置材质
              mr.material = new HDRLitMaterial();
              // 添加到场景
              scene3D.addChild(obj);
              // 创建前向渲染
              let renderJob: ForwardRenderJob = new ForwardRenderJob(scene3D);
              // 开始渲染
              Engine3D.startRender(renderJob);
          }
      }
      

      完成项目

      可以看到,创建完一个项目后,只添加了hello.ts文件和修改了App.vue。这样一个真正的3D项目开发完成了,再在浏览器里面看一下运行效果(不要忘记在canary)。

      215b515d-ee86-4b19-847c-f968b6fab428-image.png

      你得到了一个可以用鼠标操作的立方体,按住左键拖拽可以任意旋转,滑动滚动可以调整距离,按住右键拖拽可以快速调整立方体位置,可以多操作一下熟悉一下这种最常见的操作方式。
      回忆我刚运行这一步时还是有点激动的,终于迈进了3D技术的大门,尽管是借用了引擎的助力,不过我们程序员就要擅长找到适合自己的工具。

      代码解析

      作为3D小白,我的目标是始于orillusion但不会止于某一引擎,通过充分熟悉一个引擎的用法来积累3D知识。可以看到每一个类都对应着3D世界的基础概念,在这里做一个简单的对应,顺便看看一个3D世界由哪些部分组成的,当然这里只有一个基础印象即可,不必深究。

      • 立方体:我们看到的立方体,是一个基础几何体,由类BoxGeometry来创建,实例化这个类在构造函数中指定长 宽 高,引擎就会为我们绘制出一个立方体,就像我们看到的这样。
      • 相机:相机是一个比较抽象但是在3D中无处不在,可以理解成我们的眼睛,我们是通过相机来观察3D内景物的,比如前面的立方体要在相机的可视范围内我们才能看到,在这里用组件Camera3D来定义。
      • 场景:是一个容器,前面的立方体、相机等等还有其它的对象、组件都是要添加到场景内才能被引擎组织和使用的,我们创建一个3D程序必须有一个场景,我们可以通过Scene3D类创建场景

      这里只介绍了三个最基础的组成部分,几何体、相机、场景,这三个部分在每一个3D程序中都是最基础的不可少的,以后我们会逐渐深入了解他们,也将会慢慢知道更多的工具。

      小结

      这篇文章是个开篇,快速上手了一个入门级别的3D示例,主要的作用并不是学习3D的基本技能,可以说更重要的是消除疑惑,最简单的上手3D项目。不得不说orillusion没有让我失望,以我目前这么简单的诉求也不会让我失望吧。

      作为3D新手,后续会不断的记录学习过程,期待与你一起学习一起飞!

      1 条回复 最后回复 回复 引用 3
      • shuangliu
        shuangliu 最后由 shuangliu 编辑

        目前 @orillusion/core 还在内测中,正在完善文档教程,无法直接使用 npm 安装,请再等待一段时间,即将跟大家见面

        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