Hit Test API ช่วยให้คุณวางตำแหน่งไอเทมเสมือนในมุมมองโลกจริงได้
WebXR Device API เปิดตัวเมื่อฤดูใบไม้ร่วงที่ผ่านมาใน Chrome 79 ตามที่ระบุไว้ในตอนนั้น การติดตั้งใช้งาน API ของ Chrome ยังอยู่ระหว่างการพัฒนา Chrome ยินดีที่จะประกาศว่า เราได้ดำเนินการบางอย่างเสร็จสิ้นแล้ว ใน Chrome 81 มีฟีเจอร์ใหม่ 2 อย่าง ได้แก่
บทความนี้ครอบคลุม WebXR Hit Test API ซึ่งเป็น วิธีวางวัตถุเสมือนในมุมมองกล้องของโลกจริง
ในบทความนี้ ฉันจะถือว่าคุณรู้วิธีสร้างเซสชันความเป็นจริงเสริม และรู้วิธีเรียกใช้ลูปเฟรมอยู่แล้ว หากคุณไม่คุ้นเคยกับแนวคิดเหล่านี้ โปรดอ่านบทความก่อนหน้าในชุดนี้
ตัวอย่างเซสชัน AR แบบสมจริง
โค้ดในบทความนี้อิงตามโค้ดที่พบใน ตัวอย่างการทดสอบการตรวจหาของกลุ่มงาน Immersive Web (เดโม แหล่งที่มา) แต่ไม่เหมือนกัน ตัวอย่างนี้ช่วยให้คุณวางดอกทานตะวันเสมือนจริงบนพื้นผิวในโลกแห่งความจริงได้
เมื่อเปิดแอปเป็นครั้งแรก คุณจะเห็นวงกลมสีน้ำเงินที่มีจุดอยู่ตรงกลาง จุดนี้คือจุดตัดระหว่างเส้นสมมติจากอุปกรณ์ไปยังจุดในสภาพแวดล้อม โดยจะเคลื่อนที่ตามการเคลื่อนที่ของอุปกรณ์ เมื่อพบจุดตัด ระบบจะดูดวัตถุไปยังพื้นผิวต่างๆ เช่น พื้น โต๊ะ และผนัง ที่ทำเช่นนี้เนื่องจากการทดสอบการแตะจะระบุตำแหน่งและ การวางแนวของจุดตัด แต่ไม่ได้ระบุเกี่ยวกับพื้นผิว ด้วยกัน
วงกลมนี้เรียกว่าเล็ง ซึ่งเป็นรูปภาพชั่วคราวที่ช่วยในการ วางออบเจ็กต์ในเทคโนโลยีความจริงเสริม หากแตะหน้าจอ ระบบจะวางดอกทานตะวันบนพื้นผิวที่ตำแหน่งกากบาทเล็งและวางแนวของจุดกากบาทเล็ง ไม่ว่าคุณจะแตะหน้าจอที่ใดก็ตาม กากบาทจะเคลื่อนที่ต่อไป พร้อมกับอุปกรณ์
สร้างเส้นเล็ง
คุณต้องสร้างรูปภาพเส้นเล็งด้วยตนเอง เนื่องจากเบราว์เซอร์หรือ API ไม่ได้ให้ไว้ วิธีการโหลดและวาดภาพนั้นขึ้นอยู่กับเฟรมเวิร์ก
หากไม่ได้วาดโดยตรงโดยใช้ WebGL หรือ WebGL2 โปรดดูเอกสารประกอบของเฟรมเวิร์ก
ด้วยเหตุนี้ ฉันจึงจะไม่ลงรายละเอียดเกี่ยวกับวิธีวาดกากบาทในตัวอย่าง
ด้านล่างนี้ฉันจะแสดงโค้ด 1 บรรทัดด้วยเหตุผลเดียวเท่านั้น เพื่อให้คุณทราบว่าฉันกำลังพูดถึงอะไรเมื่อใช้ตัวแปร reticle
ในตัวอย่างโค้ดในภายหลัง
let reticle = new Gltf2Node({url: 'media/gltf/reticle/reticle.gltf'});
ขอเซสชัน
เมื่อขอเซสชัน คุณต้องขอ 'hit-test' ในอาร์เรย์
requiredFeatures ดังที่แสดงด้านล่าง
navigator.xr.requestSession('immersive-ar', {
requiredFeatures: ['local', 'hit-test']
})
.then((session) => {
// Do something with the session
});
การเข้าสู่เซสชัน
ในบทความก่อนหน้านี้ ฉันได้นำเสนอโค้ดสำหรับการเข้าสู่เซสชัน XR เราได้แสดง
เวอร์ชันของข้อความนี้ด้านล่างพร้อมส่วนที่เพิ่มเข้ามา ก่อนอื่นฉันได้เพิ่ม select event
listener เมื่อผู้ใช้แตะหน้าจอ ระบบจะวางดอกไม้ในมุมมองกล้อง
ตามท่าทางของกากบาทเล็ง ฉันจะอธิบายเครื่องฟังกิจกรรมนั้นในภายหลัง
function onSessionStarted(xrSession) {
xrSession.addEventListener('end', onSessionEnded);
xrSession.addEventListener('select', onSelect);
let canvas = document.createElement('canvas');
gl = canvas.getContext('webgl', { xrCompatible: true });
xrSession.updateRenderState({
baseLayer: new XRWebGLLayer(session, gl)
});
xrSession.requestReferenceSpace('viewer').then((refSpace) => {
xrViewerSpace = refSpace;
xrSession.requestHitTestSource({ space: xrViewerSpace })
.then((hitTestSource) => {
xrHitTestSource = hitTestSource;
});
});
xrSession.requestReferenceSpace('local').then((refSpace) => {
xrRefSpace = refSpace;
xrSession.requestAnimationFrame(onXRFrame);
});
}
พื้นที่อ้างอิงหลายรายการ
โปรดสังเกตว่าโค้ดที่ไฮไลต์เรียกใช้ XRSession.requestReferenceSpace()
2 ครั้ง ตอนแรกฉันก็สับสนกับเรื่องนี้ ฉันถามว่าเหตุใดโค้ดทดสอบการแตะจึงไม่
ขอเฟรมภาพเคลื่อนไหว (เริ่มลูปเฟรม) และเหตุใดลูปเฟรม
จึงดูเหมือนไม่เกี่ยวข้องกับการทดสอบการแตะ สาเหตุของความสับสนคือ
ความเข้าใจผิดเกี่ยวกับพื้นที่อ้างอิง พื้นที่อ้างอิงแสดงความสัมพันธ์
ระหว่างต้นทางกับโลก
หากต้องการทำความเข้าใจว่าโค้ดนี้ทำอะไร ให้ลองจินตนาการว่าคุณกำลังดูตัวอย่างนี้ โดยใช้ริกแบบสแตนด์อโลน และมีทั้งชุดหูฟังและคอนโทรลเลอร์ หากต้องการวัด ระยะทางจากคอนโทรลเลอร์ คุณจะต้องใช้กรอบอ้างอิง ที่คอนโทรลเลอร์เป็นศูนย์กลาง แต่หากต้องการวาดอะไรบางอย่างลงบนหน้าจอ คุณจะต้องใช้พิกัดที่เน้นผู้ใช้เป็นหลัก
ในตัวอย่างนี้ ผู้ชมและผู้ควบคุมคืออุปกรณ์เดียวกัน แต่ฉันมี ปัญหา สิ่งที่ฉันวาดต้องคงที่เมื่อเทียบกับสภาพแวดล้อม แต่ "ตัวควบคุม" ที่ฉันใช้วาดนั้นเคลื่อนที่
สำหรับการวาดภาพ ฉันใช้localพื้นที่อ้างอิง ซึ่งช่วยให้ฉันมีความเสถียร
ในแง่ของสภาพแวดล้อม หลังจากได้รับข้อมูลนี้แล้ว ฉันจะเริ่มวนซ้ำเฟรมโดยเรียกใช้ requestAnimationFrame()
สำหรับการทดสอบการตรวจหาออบเจ็กต์ ฉันใช้viewerพื้นที่อ้างอิงซึ่งอิงตาม
ท่าทางของอุปกรณ์ในเวลาที่ทำการทดสอบการตรวจหาออบเจ็กต์ ป้ายกำกับ "ผู้ชม" ค่อนข้าง
สับสนในบริบทนี้เนื่องจากฉันกำลังพูดถึงคอนโทรลเลอร์ ซึ่งก็สมเหตุสมผล
หากคุณคิดว่าตัวควบคุมเป็นเหมือนผู้ชมที่เป็นอิเล็กทรอนิกส์ หลังจากได้รับข้อมูลนี้แล้ว ฉันจะ
เรียกใช้ xrSession.requestHitTestSource() ซึ่งจะสร้างแหล่งข้อมูลการทดสอบการแตะ
ที่ฉันจะใช้เมื่อวาด
การวนซ้ำเฟรม
requestAnimationFrame() Callback ยังได้รับโค้ดใหม่เพื่อจัดการการทดสอบการแตะด้วย
ขณะที่คุณเคลื่อนอุปกรณ์ กากบาทเล็งต้องเคลื่อนตามไปด้วยขณะที่พยายามค้นหาพื้นผิว
หากต้องการสร้างภาพลวงตาของการเคลื่อนไหว ให้วาดเส้นเล็งใหม่ในทุกเฟรม
แต่ไม่ต้องแสดงกากบาทเล็งหากการทดสอบการตรวจหาการสัมผัสล้มเหลว ดังนั้น สำหรับกากบาทที่ฉันสร้างขึ้น
ก่อนหน้านี้ ฉันจึงตั้งค่าพร็อพเพอร์ตี้ visible เป็น false
function onXRFrame(hrTime, xrFrame) {
let xrSession = xrFrame.session;
xrSession.requestAnimationFrame(onXRFrame);
let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);
reticle.visible = false;
// Reminder: the hitTestSource was acquired during onSessionStart()
if (xrHitTestSource && xrViewerPose) {
let hitTestResults = xrFrame.getHitTestResults(xrHitTestSource);
if (hitTestResults.length > 0) {
let pose = hitTestResults[0].getPose(xrRefSpace);
reticle.visible = true;
reticle.matrix = pose.transform.matrix;
}
}
// Draw to the screen
}
หากต้องการวาดอะไรก็ตามใน AR ฉันต้องรู้ว่าผู้ชมอยู่ที่ไหนและกำลังมองไปที่ใด
ดังนั้นฉันจึงทดสอบว่า hitTestSource และ xrViewerPose ยังคงใช้งานได้
function onXRFrame(hrTime, xrFrame) {
let xrSession = xrFrame.session;
xrSession.requestAnimationFrame(onXRFrame);
let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);
reticle.visible = false;
// Reminder: the hitTestSource was acquired during onSessionStart()
if (xrHitTestSource && xrViewerPose) {
let hitTestResults = xrFrame.getHitTestResults(xrHitTestSource);
if (hitTestResults.length > 0) {
let pose = hitTestResults[0].getPose(xrRefSpace);
reticle.visible = true;
reticle.matrix = pose.transform.matrix;
}
}
// Draw to the screen
}
ตอนนี้ฉันเรียก getHitTestResults() โดยรับ hitTestSource เป็นอาร์กิวเมนต์
และแสดงผลอาร์เรย์ของอินสแตนซ์ HitTestResult การทดสอบการแตะอาจพบ
พื้นผิวหลายรายการ โดยรูปภาพแรกในอาร์เรย์คือรูปภาพที่อยู่ใกล้กล้องมากที่สุด
โดยส่วนใหญ่คุณจะได้ใช้ฟังก์ชันนี้ แต่ระบบจะแสดงผลอาร์เรย์สำหรับการใช้งานขั้นสูง
ตัวอย่างเช่น สมมติว่ากล้องเล็งไปที่กล่องบนโต๊ะที่อยู่บนพื้น
การทดสอบการตรวจหาอาจแสดงพื้นผิวทั้ง 3 แบบในอาร์เรย์ ในกรณีส่วนใหญ่ จะเป็นกล่องที่ฉันสนใจ หากความยาวของอาร์เรย์ที่ส่งคืนเป็น 0 กล่าวคือ หากไม่มีการทดสอบการแตะที่ส่งคืน ให้ดำเนินการต่อไป โปรดลองอีกครั้งในเฟรมถัดไป
function onXRFrame(hrTime, xrFrame) {
let xrSession = xrFrame.session;
xrSession.requestAnimationFrame(onXRFrame);
let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);
reticle.visible = false;
// Reminder: the hitTestSource was acquired during onSessionStart()
if (xrHitTestSource && xrViewerPose) {
let hitTestResults = xrFrame.getHitTestResults(xrHitTestSource);
if (hitTestResults.length > 0) {
let pose = hitTestResults[0].getPose(xrRefSpace);
reticle.visible = true;
reticle.matrix = pose.transform.matrix;
}
}
// Draw to the screen
}
สุดท้าย ฉันต้องประมวลผลผลการทดสอบการตรวจหา กระบวนการพื้นฐานมีดังนี้ รับ
ท่าทางจากผลการทดสอบการยิง เปลี่ยน (ย้าย) รูปภาพเล็งไปยังตำแหน่งการทดสอบการยิง แล้วตั้งค่าพร็อพเพอร์ตี้ visible เป็นจริง ท่าทางแสดงถึง
ท่าทางของจุดบนพื้นผิว
function onXRFrame(hrTime, xrFrame) {
let xrSession = xrFrame.session;
xrSession.requestAnimationFrame(onXRFrame);
let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);
reticle.visible = false;
// Reminder: the hitTestSource was acquired during onSessionStart()
if (xrHitTestSource && xrViewerPose) {
let hitTestResults = xrFrame.getHitTestResults(xrHitTestSource);
if (hitTestResults.length > 0) {
let pose = hitTestResults[0].getPose(xrRefSpace);
reticle.matrix = pose.transform.matrix;
reticle.visible = true;
}
}
// Draw to the screen
}
การวางออบเจ็กต์
ระบบจะวางออบเจ็กต์ใน AR เมื่อผู้ใช้แตะหน้าจอ ฉันได้เพิ่มตัวแฮนเดิลเหตุการณ์ select ลงในเซสชันแล้ว (ดูด้านบน)
สิ่งสำคัญในขั้นตอนนี้คือการรู้ว่าควรวางปุ่มไว้ที่ใด เนื่องจากกากบาทเล็งที่เคลื่อนไหวจะให้แหล่งที่มาของการทดสอบการแตะอย่างต่อเนื่อง วิธีที่ง่ายที่สุดในการวางออบเจ็กต์คือการวาดออบเจ็กต์ที่ตำแหน่งของกากบาทเล็งในการทดสอบการแตะครั้งล่าสุด
function onSelect(event) {
if (reticle.visible) {
// The reticle should already be positioned at the latest hit point,
// so we can just use its matrix to save an unnecessary call to
// event.frame.getHitTestResults.
addARObjectAt(reticle.matrix);
}
}
บทสรุป
วิธีที่ดีที่สุดในการทำความเข้าใจเรื่องนี้คือการดูโค้ดตัวอย่างหรือลองใช้Codelab หวังว่า เราได้ให้ข้อมูลพื้นฐานแก่คุณมากพอที่จะเข้าใจทั้ง 2 อย่าง
เรายังสร้าง Immersive Web API ไม่เสร็จ และยังต้องพัฒนาอีกมาก เราจะเผยแพร่ บทความใหม่ๆ ที่นี่เมื่อมีความคืบหน้า
รูปภาพโดย Daniel Frank ใน Unsplash