Hit Test API ช่วยให้คุณจัดวางไอเทมเสมือนจริงในมุมมองของชีวิตจริงได้
WebXR Device API จัดส่งไปเมื่อฤดูใบไม้ร่วงที่ผ่านมาใน Chrome 79 ตามที่ได้แจ้งไปก่อนหน้านี้ การใช้งาน API ของ Chrome ยังอยู่ระหว่างดำเนินการ Chrome ยินดีที่จะประกาศว่างานบางส่วนเสร็จสมบูรณ์แล้ว Chrome 81 มีฟีเจอร์ใหม่ 2 รายการ ได้แก่
บทความนี้กล่าวถึง WebXR Hit Test API ซึ่งเป็นวิธีวางวัตถุเสมือนในมุมมองกล้องในชีวิตจริง
ในบทความนี้ เราถือว่าคุณทราบวิธีสร้างเซสชันเทคโนโลยีความจริงเสริมและวิธีเรียกใช้เฟรมวนแล้ว หากคุณไม่คุ้นเคยกับแนวคิดเหล่านี้ คุณควรอ่านบทความก่อนหน้าในชุดนี้
- Virtual Reality มาถึงเว็บแล้ว
- เทคโนโลยีความจริงเสมือน (VR) มาสู่เว็บ ตอนที่ 2
- Web AR: คุณอาจทราบวิธีใช้อยู่แล้ว
ตัวอย่างเซสชัน AR แบบสมจริง
โค้ดในบทความนี้อิงตามตัวอย่างการทดสอบ Hit Test ของกลุ่มทํางานเว็บที่สมจริง (Immersive Web Working Group) แต่ไม่ได้เหมือนกันทั้งหมด (demo, source) ตัวอย่างนี้ช่วยให้คุณวางดอกทานตะวันเสมือนจริงบนพื้นผิวในชีวิตจริงได้
เมื่อเปิดแอปเป็นครั้งแรก คุณจะเห็นวงกลมสีน้ำเงินที่มีจุดอยู่ตรงกลาง จุดคือจุดตัดระหว่างเส้นสมมติจากอุปกรณ์ของคุณไปยังจุดในสภาพแวดล้อม ซึ่งจะเคลื่อนไหวตามที่คุณย้ายอุปกรณ์ เมื่อพบจุดตัด ดูเหมือนว่าอุปกรณ์จะยึดติดกับพื้นผิวต่างๆ เช่น พื้น โต๊ะ และผนัง ที่เป็นเช่นนี้เพราะการทดสอบ Hit จะระบุตำแหน่งและการวางแนวของจุดตัด แต่ไม่มีอะไรเกี่ยวกับพื้นผิวของตัวเอง
วงกลมนี้เรียกว่าเรติเคิล ซึ่งเป็นรูปภาพชั่วคราวที่จะช่วยในการวางวัตถุในเทคโนโลยีความจริงเสริม หากคุณแตะหน้าจอ ระบบจะวางดอกทานตะวันบนพื้นผิว ณ ตําแหน่งภาพเล็งและการวางแนวของจุดภาพเล็ง ไม่ว่าคุณจะแตะหน้าจอที่ใดก็ตาม เส้นเล็งจะเคลื่อนไหวตามอุปกรณ์ของคุณ
สร้างรูรับแสง
คุณต้องสร้างรูปภาพ Reticle เองเนื่องจากไม่ได้ให้โดยเบราว์เซอร์หรือ 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 เมื่อผู้ใช้แตะหน้าจอ ระบบจะวางดอกไม้ในมุมมองของกล้องตามตำแหน่งของเส้นเล็ง ฉันจะอธิบาย 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 ครั้ง ตอนแรกฉันก็รู้สึกสับสนกับเรื่องนี้ เราถามว่าเหตุใดโค้ด Hit Test จึงไม่ขอเฟรมภาพเคลื่อนไหว (เริ่มเฟรมวน) และเหตุใดเฟรมวนจึงดูเหมือนจะไม่เกี่ยวข้องกับ Hit Test ความสับสนนี้เกิดจากความเข้าใจผิดเกี่ยวกับพื้นที่อ้างอิง พื้นที่อ้างอิงแสดงความสัมพันธ์
ระหว่างแหล่งกำเนิดกับโลก
หากต้องการทำความเข้าใจสิ่งที่โค้ดนี้ทํา ให้สมมติว่าคุณกําลังดูตัวอย่างนี้โดยใช้อุปกรณ์แบบสแตนด์อโลน และมีทั้งชุดหูฟังและตัวควบคุม หากต้องการวัดระยะห่างจากตัวควบคุม คุณจะใช้เฟรมอ้างอิงแบบศูนย์กลางตัวควบคุม แต่หากต้องการวาดบางสิ่งลงในหน้าจอ คุณจะต้องใช้พิกัดที่มีผู้ใช้เป็นศูนย์กลาง
ในตัวอย่างนี้ ผู้ชมและตัวควบคุมเป็นอุปกรณ์เดียวกัน แต่ฉันพบปัญหา สิ่งที่ฉันวาดต้องคงที่เมื่อเทียบกับสภาพแวดล้อม แต่ "ตัวควบคุม" ที่ใช้วาดนั้นเคลื่อนไหว
สําหรับการวาดภาพ ฉันใช้พื้นที่อ้างอิง local
ซึ่งช่วยให้ฉันมีเสถียรภาพในแง่ของสภาพแวดล้อม หลังจากได้รับข้อมูลนี้แล้ว ฉันจะเริ่มการวนเฟรมโดยเรียกใช้ requestAnimationFrame()
สําหรับการทดสอบการทํางานขององค์ประกอบที่เลือก จะใช้viewer
พื้นที่อ้างอิงซึ่งอิงตามลักษณะการวางตัวของอุปกรณ์ ณ เวลาที่ทำการทดสอบ ป้ายกำกับ "ผู้ดู" ค่อนข้างทำให้สับสนในบริบทนี้เนื่องจากเรากำลังพูดถึงตัวควบคุม ซึ่งก็สมเหตุสมผลเมื่อคุณคิดว่าตัวควบคุมเป็นเครื่องมือดูภาพอิเล็กทรอนิกส์ หลังจากได้รับข้อมูลนี้แล้ว ฉันจะเรียกใช้ xrSession.requestHitTestSource()
ซึ่งจะสร้างแหล่งที่มาของข้อมูลการทดสอบการแตะที่จะใช้เมื่อวาด
การเรียกใช้เฟรมวน
นอกจากนี้ ฟังก์ชันการเรียกกลับ requestAnimationFrame()
ยังได้รับโค้ดใหม่เพื่อจัดการการทดสอบ Hit ด้วย
เมื่อคุณย้ายอุปกรณ์ เส้นเล็งต้องขยับตามไปด้วยขณะพยายามค้นหาพื้นผิว หากต้องการสร้างภาพลวงตาของการเคลื่อนไหว ให้วาดเส้นเล็งอีกครั้งในทุกเฟรม
แต่อย่าแสดงเส้นเล็งหากการทดสอบการตีไม่สำเร็จ ดังนั้นสําหรับเป้าเล็บที่สร้างขึ้นก่อนหน้านี้ ฉันจึงตั้งค่าพร็อพเพอร์ตี้ 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 หรือหากไม่มีการทดสอบ Hit ให้ดำเนินการต่อ ลองอีกครั้งในเฟรมถัดไป
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
}
สุดท้าย เราต้องประมวลผลผลการทดสอบ Hit กระบวนการพื้นฐานมีดังนี้ โพสท่าจากผลการทดสอบ Hit แปลง (ย้าย) รูปภาพเส้นตรงไปยังตำแหน่งทดสอบ Hit แล้วตั้งค่าพร็อพเพอร์ตี้ 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 รายการ
เรายังไม่หยุดสร้าง Web API แบบสมจริง เราจะเผยแพร่บทความใหม่ที่นี่เมื่อดำเนินการเสร็จสิ้น
รูปภาพโดย Daniel Frank ใน Unsplash