简介
Oasis 是蚂蚁集团开源的自研高性能Web图形引擎。这篇文章分享下在Oasis中如何实现高精度的拾取功能。
拾取的两个实现方案
关于拾取功能,其实有两种实现方案,各自适用于不同的应用场景。
方案一:射线包围盒
简介
射线包围盒是一种常用的方案,适用于没有像素级拾取需求的场景。官方基于此封装了基本的输入交互 - Oasis Engine
从图中可以看出这种方案有两个关键点
- 创建射线
- 添加碰撞体 而因为内置的碰撞体只有几种简单集合体形状,所以很难实现对复杂形状物体的高精度拾取。
优点
- 性能较好
缺点
- 精度较差
方案二:帧缓冲拾取
能够实现像素级的高精度拾取功能。但会更耗性能,如果拾取的频率过高,需要评估性能开销是否适合应用场景。
优点
- 高精度
缺点
- 耗性能(因为底层会进行 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>
下面是实现效果~
感谢观看,有问题欢迎指出~