什么?浏览器也能搞 VR 了?aframe 让你通过 react/vue/svelte/原生HTML 写 VR 项目!

aframe 是什么?

AFrame 是基于 three.js 进行高度封装的 3D JavaScript 框架,让我们可以通过 HTML 标签的方式快速的写出一个 3D 项目。

且它还集成了 WebVR 相关的配置,你写的项目可以直接通过点击右下角按钮投射到 VR 上,让你无需进行 WebVR 兼容直接开启 VR 模式。它目前支持了大多数主流 VR 设备的投射,且封装了很多手柄的功能。

本来是打算先讲讲原理的,但说一千道一万,都不如直接一个 Demo 来的实在。

原理我放在了最后一个章节。

快速体验

想到 VR 这种新技术,就喜上心头,快来让我们赶紧试试吧!

简单 Demo

  1. 我们第一步通过 script 标签引入 HTML 中:
html 复制代码
<script src="https://aframe.io/releases/1.4.0/aframe.min.js"></script>
  1. 第二步添加一个场景(场景就类似 HTML 的 body,对于 aframe 是必需品):
html 复制代码
<body>
  <a-scene>
  </a-scene>
</body>
  1. 第三步添加一些组件,如天空、盒子、球 等.. :
html 复制代码
  <a-scene>
    <a-box position="-1 0.5 -3" rotation="0 45 0" color="#4CC3D9"></a-box>
    <a-sphere position="0 1.25 -5" radius="1.25" color="#EF2D5E"></a-sphere>
    <a-cylinder position="1 0.75 -3" radius="0.5" height="1.5" color="#FFC65D"></a-cylinder>
    <a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4"></a-plane>
    <a-sky color="#ECECEC"></a-sky>
  </a-scene>

finished! 到这里一个简单的项目就完成了,让我们打开文件看看吧!

源码

你也可以直接复制到自己的文件里,然后文件名改为 .html 使用。

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <script src="https://aframe.io/releases/1.4.0/aframe.min.js"></script>
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>VR</title>
</head>
<body>
  <a-scene>
    <a-box position="-1 0.5 -3" rotation="0 45 0" color="#4CC3D9"></a-box>
    <a-sphere position="0 1.25 -5" radius="1.25" color="#EF2D5E"></a-sphere>
    <a-cylinder position="1 0.75 -3" radius="0.5" height="1.5" color="#FFC65D"></a-cylinder>
    <a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4"></a-plane>
    <a-sky color="#ECECEC"></a-sky>
  </a-scene>
</body>
</html>

连接 VR(PICO)

上面我们只是把 3D 做出来了,想要投射到 VR 上,还需要一些必不可少的步骤。

由于我手里只有 Pico 的设备,我这里就拿 Pico 举例(国内也是 Pico 流行度更高)。

介绍

Pico 及常见的 VR 设备通常情况下,都是直接戴上 VR 设备就开始使用,这时候我们跑的数据是通过 Pico 的处理器及内存来运行。

但我们使用 aframe 将浏览器的内容投射到 VR 设备上时,就出现了差异。

我们本质上跑的数据还是在电脑上,然后将画面投射到 VR 设备中,这里其实是电脑上浏览器调用了 WebXR。

因此,我们想要跑通,就需要在本地通过 Wifi/数据线 将设备和电脑连接起来。Pico 官网提供了 《游戏串流助手》 来帮助我们进行连接。

游戏串流助手 下载地址:www.picoxr.com/cn/software... steam 下载地址:store.steampowered.com/

前置安装

我们首先要下载并安装游戏串流助手、steam,这两者的安装都是傻瓜式,不再需要独立教程。

然后我们要在 steam 的商店中搜索 steam VR(游戏串流助手会吊起 steam VR 用于连接)并安装。

连接

条件:

  • 软件
    • steam(安装 steam VR)
    • 游戏串流助手(电脑端)
    • 游戏串流助手(Pico 端)
  • 硬件
    • 电脑(有 USB 3.0 接口)
    • USB 3.0 数据线(Pico 设备会提供)
    • Pico VR 设备
  1. 硬件连接:将 USB 3.0 数据线连接到电脑的 USB 3.0 接口,另一头接入 Pico 设备
  2. 电脑开启 游戏串流助手
  3. 点击 USB 数据线继续
  4. 进入 Pico,打开游戏串流助手
  5. 点击 《可串流设备》 下面的对应设备后面的连接按钮
  6. 连接成功

Pico 连接成功表现

  • Pico 中进入了一个黑色的世界
  • 电脑端游戏串流助手显示该内容
  • steam VR 显示以下内容

投射到 VR

连接成功后,戴上 VR 设备,然后打开我们前面做的网页,网页的右下角会有一个 VR 按钮。

点击该按钮投射到 VR 设备,投射效果如下:

此时 steamVR 显示:

到这里我们从开发到投射到 VR 的主流程就完成了。

学习

想要写好 aframe , 关键点有三:

  • 声明式 HTML(就像我们平时写 Vue、React,调用的组件库,Aframe 给我们提供了一套组件)
  • 自定义组件(和传统意义上的组件不同,更像 Vue、React 的 Hooks,用来写通用逻辑)
  • Scene 标签(一个项目的根节点,很多配置都是在该标签上配置的)

场景 scene

场景由 a-scene 表示,场景是全局的根对象,它帮我们处理所有 Three.js 和 WebXR 的内容。这些内容包括但不限于:

  • 置画布、渲染器
  • 默认的相机灯光
  • 设置 webvr-polyfill、VREffect
  • 添加调用 WebVR API 的进入 VR 的 UI
  • webXR 通过系统配置 WebXR 设备

还有包括像我们 cursor 开启鼠标事件、physics 开启物理引擎、 embedded 是否镶嵌在 HTML 中等,都由 a-scene 提供。

文档地址:aframe.io/docs/1.4.0/...

声明式 HTML

AFRAME 和传统的 untity 开发类似,都采用了实体组件系统。不同的是 AFRAME 是基于 DOM 的 ECS,这样是对前端友好的操作。

举个例子,我们想要一个 box 的组件: box = 几何 + 位置 + 材质

基于此写我们的代码:

ini 复制代码
<a-entity
    geometry="primitive: box; width: .5; height: .5; depth: .5"
    material="color: #4CC3D9"
    position="0 0.5 -3"
/>
<a-entity camera="" />

这样便做了一个盒子。

我们可以看到,这里和传统的前端框架是一致的,我们可以把 a-entity 理解成一个空 div,我们需要一个方块,我们定义这个方块的 大小、位置、颜色。

但不同的是,它的属性并不是传进去给内部使用的,而是一个组件的入参,具体细节我们在组件中说。

自定义组件

上面我们说了如何在 AFRAME 中创建一个 BOX,我们上章说到 BOX 中的 geometry 、position 等参数不是传统前端上的给组件内传参。

聊到自定义组件,我们得先明白什么是组件。带着前端的概念,就会觉得 HTML + script 组合成了一个组件。

但 AFRAME 的自定义组件并不是这个东西。拿 Vue 举例: template + options API 组合成了一个组件。对于 AFRAME 来说,只有 options API 是组件本体,而 template 是挂载组件的位置。

注意: 千万不要将 AFRAME 的组件和传统前端框架的组件和混为一谈。如果理解不够深刻,可以查看 [编写组件] 章节复习。

举个例子

我们现在有一个 box 的 声明式HTML:<a-box position="0 0.5 -3">

这样一段代码中,position 是组件的引用。至于这个组件是哪来的,我们去看看 AFRAME 源码中的 position 组件。

kotlin 复制代码
module.exports.Component = registerComponent('position', {
  schema: {type: 'vec3'},

  update: function () {
    var object3D = this.el.object3D;
    var data = this.data;
    object3D.position.set(data.x, data.y, data.z);
  },

  remove: function () {
    // Pretty much for mixins.
    this.el.object3D.position.set(0, 0, 0);
  }
});

自定义组件中的关键点

这其中的 schema 是定义参数的数据结构限制,这里代表 position 的参数类型是 vec3 的类型。

而 Update 、Remove 则是组件的生命周期。

组件中既有 属性 和 绑定属性的元素节点,以及生命周期的 hook。

  • 属性:
    • 属性可以通过 schema 约定参数类型,通过 this.data 获取传参,同时在生命周期中也可以被调用。
  • 元素:
    • 元素可以通过 this.el 获取,同时也可以在生命周期中被获取,可以通过 addEventListener 添加事件。
  • 自定义组件生命周期:
    • Init 初始化调用
    • Update 数据发生变化时被调用
    • Tick 每帧调用
    • Remove 组件从实体移除时被调用 - Pause 实体被暂停或恢复

写一个自定义组件

我们上面有了一个盒子,我们现在想让盒子点击后变色,且我们不想污染自己代码。我们便可以编写一个 change-color-on-click 组件。

javascript 复制代码
AFRAME.registerComponent("change-color-on-click", {
  init: function () {
    this.el.addEventListener("click", (e) => {
      console.log(e);
      this.el.setAttribute(
        "material",
        "color",
        "#" + Math.floor(Math.random() * 16777215).toString(16)
      );
    });
  },
});

这步做完,我们需要将组件挂在在我们的盒子上。

ini 复制代码
<a-entity
  geometry="primitive: box; width: .5; height: .5; depth: .5"
  material="color: #4CC3D9"
  position="0 0.5 -3"
  rotation="0 45 0"
  change-color-on-click
></a-entity>

这时候我们会发现点击事件没有生效,我们需要添加 cursor="rayOrigin: mouse" 代码到 a-scene 上以开启事件。

ini 复制代码
<a-scene cursor="rayOrigin: mouse">
  ...
</a-scene>

重要标签

还有一些比较重要的 AFRAME 提供的标签我们需要了解:

  • 核心:
    • a-assets: 例如我们的图片、3D资源,都要在 assets 中引入
  • 额外:
    • a-camera:相机
    • a-light:灯光
    • a-plane:一块板(比如地面)
    • a-sky:天空

💡 !注意:相机的 position 只在非 VR 场景生效:

在 VR 的场景中,position 由人物所在位置决定。 如果需要修改位置,可以通过外面包一个只有位置的空 entity 用来控制相机位置。 同时请注意将手柄也放在该元素内,否则会出现手柄未跟随导致的丢失的问题。

VR 相关

  • 隐藏右下角按钮 :vr-mode-ui="enabled: false"
  • 手柄组件 :laser-controls 、 tracked-controls 、 windows-motion-controls
  • 手柄事件 :thumbstickmoved、buttondown
  • 进入页面自动开启 VR
    • 由于浏览器的安全策略,直接在页面加载时进入 VR 模式不被允许。

经过测试,无论是直接调用、先调用 requestSession 再调用、模拟 click 点击后调用(js代码触发) 均无法实现。

💡 脏方案: 通过在 VR 资源加载之前的点击事件,添加 interval 间隔 500 毫秒检测 a-scene 是否加载成功,加载成功后,通过 enterVR 进入 VR,通过检测 is('vr-mode') 判断是否进入 vr 模式,如果进入,清除定时器。

实现代码:

javascript 复制代码
function openVR() {
  const flag = setInterval(() => {
    const sceneEl = document.querySelector("a-scene");

    if (!sceneEl) return;
    sceneEl.enterVR();
    if (sceneEl.is("vr-mode")) clearInterval(flag);
  }, 500);
}
  • 部分情况,浏览器无法进入 VR

    • 暂未知道原因,可以通过 chrome 的快捷方式添加后缀的方式解决。
    • 举例:
      1. 快捷方式的目标是 "C:\Program Files\Google\Chrome\Application\chrome.exe"
      2. 打开快捷方式的属性
      3. 目标参数后添加 -no-sandbox ,注意 - 前有一个空格
      4. 点击确定
      5. 通过快捷方式进入浏览器
      6. 浏览器显示以下横幅表示设置成功
  • 检测是否进入 VR 模式

    • document.querySelector('a-scene').is('vr-mode')
  • 检测是否连接了 VR 设备(目前方案精准度不足)(调研到了精准度更高的方案,后续单独讲讲) - AFRAME.utils.device.checkHeadsetConnected()

    • 文档:aframe.io/docs/master...

    • 源码:github.com/aframevr/af...

    • 通过源码查它右下角的按钮,发现它通过 utils.devicecheckVRSupport 检查 VR 设备支持。

    • 但该接口在声明文件以及文档中未定义,所以会报错。

    • 该代码在源码中的位置:

      csharp 复制代码
      scr/component/scene/xr-model-ui.js    185行
      if (!utils.device.checkVRSupport()) { this.enterVREl.classList.add('fullscreen'); }
    • 该接口返回的是 supportsVRSession 变量,该变量定义在

      ini 复制代码
      scr/utils/device.js  94line
      
      function checkVRSupport () { return supportsVRSession; }
      module.exports.checkVRSupport = checkVRSupport;

上面是此次开发用到的功能,其他功能可以查看 AFRAME 文档里 core 中的 Utils。

第一个项目

我们尝试写一个可以 自由随机移动 + 点击变色的盒子。

新建 HTML,并添加 AFRAME 1.2 版本的引入

xml 复制代码
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script src="<https://aframe.io/releases/1.2.0/aframe.min.js>"></script>
    <title>Document</title>
  </head>
  <body></body>
</html>

添加场景,并开启点击事件

ini 复制代码
<a-scene cursor="rayOrigin: mouse">
</a-scene>

添加一个 .5 的正方体盒子

ini 复制代码
<a-box
  width="0.5"
  height="0.5"
  depth="0.5"
  material="color: #4CC3D9"
  position="0 0.5 -3"
  rotation="0 45 0"
></a-box>

组件开发:点击变色

xml 复制代码
<script>
  AFRAME.registerComponent("change-color-on-click", {
    init: function () {
      this.el.addEventListener("click", (e) => {
        console.log(e);
        this.el.setAttribute(
          "material",
          "color",
          "#" + Math.floor(Math.random() * 16777215).toString(16)
        );
      });
    },
  });
</script>

组件开发:随机移动(间隔帧由外部传入)

kotlin 复制代码
AFRAME.registerComponent("random-move", {
  init: function () {
    this.tickCount = 0;
  },

  tick: function () {
    this.tickCount++;

    if (this.tickCount % this.data.frameInterval !== 0) return;

    var el = this.el;
    var position = el.getAttribute("position");

    position.x += (Math.random() - 0.5) * 0.1;
    position.y += (Math.random() - 0.5) * 0.1;
    position.z += (Math.random() - 0.5) * 0.1;

    el.setAttribute("position", position);
  },
});

给 BOX 添加组件

ini 复制代码
<a-box
  width="0.5"
  height="0.5"
  depth="0.5"
  material="color: #4CC3D9"
  position="0 0.5 -3"
  rotation="0 45 0"
  random-move="frameInterval: 10"
	change-color-on-click
></a-box>

完整代码

● 源码:

xml 复制代码
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>

    <script>
      AFRAME.registerComponent("change-color-on-click", {
        init: function () {
          this.el.addEventListener("click", (e) => {
            console.log(e);
            this.el.setAttribute(
              "material",
              "color",
              "#" + Math.floor(Math.random() * 16777215).toString(16)
            );
          });
        },
      });

      AFRAME.registerComponent("random-move", {
        init: function () {
          this.tickCount = 0;
        },

        tick: function () {
          this.tickCount++;

          if (this.tickCount % this.data.frameInterval !== 0) return;

          var el = this.el;
          var position = el.getAttribute("position");

          position.x += (Math.random() - 0.5) * 0.1;
          position.y += (Math.random() - 0.5) * 0.1;
          position.z += (Math.random() - 0.5) * 0.1;

          el.setAttribute("position", position);
        },
      });
    </script>

    <title>Document</title>
  </head>
  <body>
    <a-scene cursor="rayOrigin: mouse">
      <a-box
        class="box"
        width="0.5"
        height="0.5"
        depth="0.5"
        material="color: #4CC3D9"
        position="0 0.5 -3"
        rotation="0 45 0"
        random-move="frameInterval: 10"
        change-color-on-click
      ></a-box>
    </a-scene>
  </body>
</html>

aframe 原理

架构图

AFRAME 是一个高度封装的 ,作为 3D + VR 的前端框架,我们优先了解它依赖的 3D 以及 VR 相关库的一些基本概念。

  • AFRAME 关系图:

基础概念

这里大部分都是针对底层实现的了解,如果仅关心使用,可忽略。

OpenGL (Open Graphics Library 开放图形库)

我们这里简单说说 OpenGL ,OpenGL 实际上是图形驱动提供的接口,用于渲染 2D、3D图形的接口。通常是由底层硬件实现的,一般情况下由硬件提供商(nvidia、amd、intel...)在图形驱动层实现,供给其他人调用。

在前端而言,它是透明的,由浏览器自己调用,所以我们在这里仅做了解。

前端无论是 CSS3 的硬件加速、canvas2D、WebGL 底层实现都是基于 OpenGL 实现的。

Canvas2D

Canvas 是通过 HTML 的 canvas 标签开始,基于 HTML5 规范,实现的接口。通常情况下我们单说 Canvas 指的都是 Canvas2D 。我们可以通过获取 Canvas 标签,在通过 HTML5 提供的接口获取 Canvas 上下文,然后通过 Context 进行绘制。

Three.JS

Three.js 是建立在 WebGL 基础上的库,提供了简单的方式创建和显示 3D 图形,针对 WebGL 这种底层的 API 进行了抽象,降低了复杂度,让我们可以更轻松的创建复杂的 3D 图形。

同时,也包括我们 3D 中常用的 灯光、阴影、纹理、复杂几何图形的功能,也提供了对 WebXR 的简单支持,提供了更简单的方式集成 WebXR ,创建 VR 、 AR 。

在 Three.js 中,我们基本上都通过 JS 代码来创建 3D 图形。

WebGL

WebGL 也是通过 HTML 的 canvas 标签开始,但 WebGL 并不是基于 HTML5 规范,而是独立规范,作为 HTML5 的一部分引入到 Web 平台。它是基于 OpenGL ES 2.0 规范实现。

我们不可以简单的将 WebGL 理解为 Canvas3D ,因为一些库(如:pixijs)底层使用 WebGL 实现 2D 绘制。因为 WebGL 提供了硬件加速功能,还可以使用更简单的 API 进行 2D 绘制。

Web XR Device API

Web XR 提供了一种方式渲染 3D场景 到 VR 或 AR 设备,同时可以访问设备的方向、位置信息、用户输入。可以在网页上实现 VR 体验。

Web XR 与具体设备无关,可以在不同的硬件上执行。

Web XR 的主要组成部分有以下几点:

  • XRSession:代表一个 XR 设备的会话,用于管理输入和输出。
  • XRReferenceSpace:定义了一个 3D 坐标系统,用于追踪设备的位置和方向。
  • XRFrame:代表一个 XR 设备的渲染帧,用于获取设备的状态和渲染图形。
  • XRInputSource:代表一个 XR 设备的输入源,如手柄或手势。

Web XR 兼容情况:

Web XR 是 W3C 的标准,所有遵守 W3C 标准的现代浏览器都提供 Web XR 的接口。因此,无论是 Chrome 、 Firfox 、 Edge 、 Safari 都可以使用 Web XR API。

AFRAME

基础概念聊完了,接下来回归正题,可以针对 AFRAME 到底是什么具体聊聊了。

官方概念

官网是这么讲的:

A web framework for building 3D/AR/VR experiences

它是用来构建 3D/AR/VR 的 Web 框架,这里面有两个关键词,一个是 3D/AR/VR ,另一个是 Web框架。

  • web 框架 :提供了一套模板,你可以根据它的模板选择你需要的东西。(后续会展开讲讲框架和库的区别)
  • 构建 3D/AR/VR :用来构建 3D/AR/VR ,将多端写在这么显眼的位置,可以想到的是,它一定在 3D/AR/VR 上做了很多兼容性的处理。
  • 官方支持的 VR 设备
    • Vive
    • Rift
    • Windows Mixed Reality
    • Daydream
    • GearVR
    • Cardboard
    • Oculus Go

特点

AFRAME 采用了模板语法 + JS + DOM操作 + 自定义组件的模式。让我们的项目开发更类似于 Svelte + DOM原生操作。作为前端开发者上手更轻松。

  • VR 支持:只需要引入 a-scene ,AFRAME 自动处理 3D 样板、VR设置、默认控件,不需要额外操作便能使用 VR。

  • 声明式 HTML(模板语法):AFRAME 可以像写 Vue 、Svelte 那样写 HTML,最终会由 AFRAME 框架自动转换为对应的 Three.js 代码,更符合前端开发者的直觉。

  • 实体组件系统(Entity-Component-System):它的实体组件系统类似于 Vue 、React 的 Hooks 或者 Mixins ,可以开发通用的逻辑,然后通过 模板语法 的属性注入到组件中。

  • 跨平台 VR:它针对 Vive、Rift、Windows Mixed Reality、Daydream、GearVR、Cardboard 做了兼容操作,支持他们的 VR 设备及控制器,不需要你针对控制器做独立适配(Pico 需要找到一些监听方法)

  • 性能:针对 Web VR 进行了性能优化,不接触浏览器布局引擎,3D对象在内存中处理。

  • 视觉检查器:可以通过 ctrl + alt + i 进入视觉检查器,它类似于 Cocos 、 Unity 这类引擎的可视化页面,但并不支持这里的代码开发等操作

  • 组件:AFRAME 提供了核心组件,同时支持社区自己开发的组件融入到你的项目中

这里面其实 VR支持、跨平台VR、性能 在我们的入门阶段不需要关心。我们在这个阶段最需要了解的是 声明式 HTML、DOM操作、自定义组件。

基本功能介绍完了,接下来我们就沿着需要关心的 声明式 HTML、DOM操作、自定义组件 展开,来讲讲我们如何写一个 AFRAME 项目吧。

关于使用 canvas2D、WebGL、Three.js、aframe 的代码量问题

下面我会基于绘画 2D 图形、3D 图形 的代码量的差异,来让大家理解 canvas2D、WebGL、Three.js、aframe 的区别以及必要性。

画个三角形

我们分别通过不同的框架来画这个三角形:
canvas2D 源码(27行)

html 复制代码
<!DOCTYPE html>
<html>
  <body>
    <canvas
      id="myCanvas"
      width="200"
      height="200"
      style="border: 1px solid #d3d3d3"
    >
      Your browser does not support the HTML5 canvas tag.
    </canvas>

    <script>
      var canvas = document.getElementById("myCanvas");
      var ctx = canvas.getContext("2d");

      ctx.beginPath();
      ctx.moveTo(50, 50);
      ctx.lineTo(100, 50);
      ctx.lineTo(75, 100);
      ctx.closePath();

      ctx.fillStyle = "red";
      ctx.fill();
    </script>
  </body>
</html>

WebGL 源码(74行)

html 复制代码
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <canvas id="canvas"></canvas>
  </body>

  <script>
    // 获取canvas元素和WebGL上下文
    var canvas = document.getElementById("canvas");
    var gl = canvas.getContext("webgl");

    // 定义顶点着色器和片元着色器
    var vertexShaderSource = `
attribute vec2 position;
void main() {
  gl_Position = vec4(position, 0.0, 1.0);
}
`;
    var fragmentShaderSource = `
void main() {
  gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);  // 红色
}
`;

    // 创建和编译着色器
    var vertexShader = gl.createShader(gl.VERTEX_SHADER);
    gl.shaderSource(vertexShader, vertexShaderSource);
    gl.compileShader(vertexShader);

    var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
    gl.shaderSource(fragmentShader, fragmentShaderSource);
    gl.compileShader(fragmentShader);

    // 创建程序并链接着色器
    var program = gl.createProgram();
    gl.attachShader(program, vertexShader);
    gl.attachShader(program, fragmentShader);
    gl.linkProgram(program);

    // 使用程序
    gl.useProgram(program);

    // 定义三角形的顶点
    var vertices = new Float32Array([
      -0.5,
      -0.5, // 第一个顶点
      0.5,
      -0.5, // 第二个顶点
      0.0,
      0.5, // 第三个顶点
    ]);

    // 创建缓冲区并上传顶点数据
    var buffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

    // 获取顶点着色器中的position变量的位置
    var positionLocation = gl.getAttribLocation(program, "position");

    // 启用顶点属性数组并指定顶点数据的布局
    gl.enableVertexAttribArray(positionLocation);
    gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);

    // 绘制三角形
    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.drawArrays(gl.TRIANGLES, 0, 3);
  </script>
</html>

Three.js 源码(27行)

html 复制代码
<!DOCTYPE html>
<html>
  <body>
    <canvas
      id="myCanvas"
      width="200"
      height="200"
      style="border: 1px solid #d3d3d3"
    >
      Your browser does not support the HTML5 canvas tag.
    </canvas>

    <script>
      var canvas = document.getElementById("myCanvas");
      var ctx = canvas.getContext("2d");

      ctx.beginPath();
      ctx.moveTo(50, 50);
      ctx.lineTo(100, 50);
      ctx.lineTo(75, 100);
      ctx.closePath();

      ctx.fillStyle = "red";
      ctx.fill();
    </script>
  </body>
</html>

aframe(15行)

html 复制代码
<!DOCTYPE html>
<html>
  <body>
    <a-scene>
      <a-triangle 
        position="0 1.5 -5" 
        rotation="180 0 0" 
        vertex-a="1 0 0" 
        vertex-b="-1 0 0" 
        vertex-c="0 1 0" 
        color="red">
      </a-triangle>
    </a-scene>
  </body>
</html>

通过上面的代码量,我们可以清晰的看到使用 aframe 的好处,就算我们只是开发 web 3D 项目,也可以尝试通过 aframe 以提高我们的开发体验。

相关推荐
吕彬-前端22 分钟前
使用vite+react+ts+Ant Design开发后台管理项目(二)
前端·react.js·前端框架
小白小白从不日白43 分钟前
react hooks--useCallback
前端·react.js·前端框架
恩婧1 小时前
React项目中使用发布订阅模式
前端·react.js·前端框架·发布订阅模式
mez_Blog1 小时前
个人小结(2.0)
前端·javascript·vue.js·学习·typescript
珊珊而川1 小时前
【浏览器面试真题】sessionStorage和localStorage
前端·javascript·面试
森叶1 小时前
Electron 安装包 asar 解压定位问题实战
前端·javascript·electron
drebander1 小时前
ubuntu 安装 chrome 及 版本匹配的 chromedriver
前端·chrome
软件技术NINI1 小时前
html知识点框架
前端·html
深情废杨杨1 小时前
前端vue-插值表达式和v-html的区别
前端·javascript·vue.js
GHUIJS1 小时前
【vue3】vue3.3新特性真香
前端·javascript·vue.js