oasis 如何实现高精度拾取

265 阅读1分钟

oasis

简介

Oasis 是蚂蚁集团开源的自研高性能Web图形引擎。这篇文章分享下在Oasis中如何实现高精度的拾取功能。

拾取的两个实现方案

关于拾取功能,其实有两种实现方案,各自适用于不同的应用场景。

方案一:射线包围盒

简介

射线包围盒是一种常用的方案,适用于没有像素级拾取需求的场景。官方基于此封装了基本的输入交互 - Oasis Engine

image.png

从图中可以看出这种方案有两个关键点

  • 创建射线
  • 添加碰撞体 而因为内置的碰撞体只有几种简单集合体形状,所以很难实现对复杂形状物体的高精度拾取。

优点

  • 性能较好

缺点

  • 精度较差

方案二:帧缓冲拾取

能够实现像素级的高精度拾取功能。但会更耗性能,如果拾取的频率过高,需要评估性能开销是否适合应用场景。

优点

  • 高精度

缺点

  • 耗性能(因为底层会进行 CPU-GPU 通信,调用 gl.readPixels 。)

使用帧缓冲拾取实现高精度拾取

以下是vue3+oasis的实现

<template>
  <canvas style="width: 100vw; height: 100vh" id="canvas" />
</template>

<script lang="ts" setup>
import { OrbitControl } from "@oasis-engine/controls";
import { onMounted, onUnmounted } from "vue";
import {
  Camera,
  Entity,
  GLTFResource,
  MeshRenderer,
  PBRMaterial,
  Scene,
  Script,
  Vector3,
  WebCanvas,
  WebGLEngine
} from "oasis-engine";
import { FramebufferPicker } from "@oasis-engine/framebuffer-picker";

//-- create engine object

let scene: Scene
let rootEntity: Entity
let modelGltf: GLTFResource
async function createOasis() {
  const engine = new WebGLEngine("canvas");
  engine.canvas.resizeByClientSize();

  scene = engine.sceneManager.activeScene;
  rootEntity = scene.createRootEntity();
  scene.ambientLight.diffuseSolidColor.setValue(1, 1, 1, 1)

  //-- create camera
  const cameraEntity = rootEntity.createChild("camera_entity");
  cameraEntity.transform.position = new Vector3(0, 4, 13);
  const camera = cameraEntity.addComponent(Camera);
  cameraEntity.addComponent(OrbitControl).target = new Vector3(0, 2, 0);

  // 加载模型
  modelGltf = await engine.resourceManager.load<GLTFResource>(`/models/a0ff1c60-5e21-49e5-946a-675515d98736.glb`);
  const modelEntity = rootEntity.createChild('modelEntity')
  modelEntity.addChild(modelGltf.defaultSceneRoot)

  // 添加拾取器
  rootEntity.addComponent(Picker).init({
    webCanvas: engine.canvas,
    camera,
    onPick: onPick,
  });

  engine.run();

}

function onPick(obj: { component: MeshRenderer; mesh?: any }) {
  if (obj && modelGltf?.materials) {
    const material = modelGltf.materials[0] as PBRMaterial
    material.baseColor.r = Math.abs(material.baseColor.r - 1)
  }
};

onMounted(() => {
  createOasis()
})
onUnmounted(() => {
  scene.destroy()
})


interface Params {
  webCanvas: WebCanvas;
  camera: Camera;
  onPick: Function;
}

/** 拾取器 */
class Picker extends Script {
  constructor(entity: Entity) {
    super(entity);
  }

  public init({ webCanvas, camera, onPick }: Params) {
    /**添加 FramebufferPicker 实现精准拾取 */
    const framebufferPicker: FramebufferPicker =
      this.entity.addComponent(FramebufferPicker);
    framebufferPicker.camera = camera;
    framebufferPicker.onPick = onPick;

    webCanvas._webCanvas.addEventListener("mousedown", (e: MouseEvent) =>
      framebufferPicker.pick(e.offsetX, e.offsetY)
    );
  }
}

</script>

下面是实现效果~

动画1.gif

感谢观看,有问题欢迎指出~