WebXR을 사용하여 몰입형 AR 세션 만들기

이 페이지에서는 WebXR을 사용하여 간단한 몰입형 AR 애플리케이션을 만드는 방법을 안내합니다.

시작하려면 WebXR 호환 개발 환경이 필요합니다.

HTML 페이지 만들기

WebXR에서는 세션을 시작하려면 사용자 상호작용이 필요합니다. activateXR()를 호출하는 버튼을 만듭니다. 페이지가 로드되면 사용자는 이 버튼을 사용하여 AR 환경을 시작할 수 있습니다.

index.html라는 새 파일을 만들고 여기에 다음 HTML 코드를 추가합니다.

<!doctype html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport"
        content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  <title>Hello WebXR!</title>

  <!-- three.js -->
  <script src="https://unpkg.com/three@0.126.0/build/three.js"></script>
</head>
<body>

<!-- Starting an immersive WebXR session requires user interaction.
    We start this one with a simple button. -->
<button onclick="activateXR()">Start Hello WebXR</button>
<script>
async function activateXR() {
  // Add a canvas element and initialize a WebGL context that is compatible with WebXR.
  const canvas = document.createElement("canvas");
  document.body.appendChild(canvas);
  const gl = canvas.getContext("webgl", {xrCompatible: true});

  // To be continued in upcoming steps.
}
</script>
</body>
</html>

three.js 초기화

시작 버튼을 누르면 별다른 일이 일어나지 않습니다. 3D 환경을 설정하려면 렌더링 라이브러리를 사용하여 장면을 표시하면 됩니다.

이 예에서는 WebGL 렌더러를 제공하는 JavaScript 3D 렌더링 라이브러리인 three.js를 사용합니다. Three.js는 렌더링, 카메라, 장면 그래프를 처리하므로 웹에 3D 콘텐츠를 더 쉽게 표시할 수 있습니다.

장면 만들기

3D 환경은 일반적으로 장면으로 모델링됩니다. AR 요소가 포함된 THREE.Scene를 만듭니다. 다음 코드를 사용하면 AR에서 불이 켜지지 않은 색상 상자를 볼 수 있습니다.

다음 코드를 activateXR() 함수 하단에 추가합니다.

const scene = new THREE.Scene();

// The cube will have a different color on each side.
const materials = [
  new THREE.MeshBasicMaterial({color: 0xff0000}),
  new THREE.MeshBasicMaterial({color: 0x0000ff}),
  new THREE.MeshBasicMaterial({color: 0x00ff00}),
  new THREE.MeshBasicMaterial({color: 0xff00ff}),
  new THREE.MeshBasicMaterial({color: 0x00ffff}),
  new THREE.MeshBasicMaterial({color: 0xffff00})
];

// Create the cube and add it to the demo scene.
const cube = new THREE.Mesh(new THREE.BoxBufferGeometry(0.2, 0.2, 0.2), materials);
cube.position.set(1, 1, 1);
scene.add(cube);

three.js를 사용하여 렌더링 설정

이 장면을 AR로 보려면 렌더러카메라가 필요합니다. 렌더러는 WebGL을 사용하여 장면을 화면에 그립니다. 카메라는 장면이 표시되는 표시 영역을 설명합니다.

다음 코드를 activateXR() 함수 하단에 추가합니다.

// Set up the WebGLRenderer, which handles rendering to the session's base layer.
const renderer = new THREE.WebGLRenderer({
  alpha: true,
  preserveDrawingBuffer: true,
  canvas: canvas,
  context: gl
});
renderer.autoClear = false;

// The API directly updates the camera matrices.
// Disable matrix auto updates so three.js doesn't attempt
// to handle the matrices independently.
const camera = new THREE.PerspectiveCamera();
camera.matrixAutoUpdate = false;

XRSession 만들기

WebXR의 진입점은 XRSystem.requestSession()을 통해 이루어집니다. immersive-ar 모드를 사용하면 렌더링된 콘텐츠를 실제 환경에서 볼 수 있습니다.

XRReferenceSpace는 가상 세계 내 객체에 사용되는 좌표계를 설명합니다. 'local' 모드는 뷰어 근처에 원점이 있고 안정적인 추적이 가능한 참조 공간이 있는 AR 환경에 가장 적합합니다.

XRSessionXRReferenceSpace를 만들려면 activateXR() 함수 하단에 다음 코드를 추가합니다.

// Initialize a WebXR session using "immersive-ar".
const session = await navigator.xr.requestSession("immersive-ar");
session.updateRenderState({
  baseLayer: new XRWebGLLayer(session, gl)
});

// A 'local' reference space has a native origin that is located
// near the viewer's position at the time the session was created.
const referenceSpace = await session.requestReferenceSpace('local');

장면 렌더링

이제 장면을 렌더링할 수 있습니다. XRSession.requestAnimationFrame()는 브라우저가 프레임을 그릴 준비가 되면 실행되는 콜백을 예약합니다.

애니메이션 프레임 콜백 중에 XRFrame.getViewerPose()를 호출하여 로컬 좌표 공간을 기준으로 한 뷰어의 포즈를 가져옵니다. 이는 렌더러가 업데이트된 카메라를 사용하여 장면을 그리기 전에 사용자가 가상 세계를 보는 방식을 변경하여 장면 내 카메라를 업데이트하는 데 사용됩니다.

다음 코드를 activateXR() 함수 하단에 추가합니다.

// Create a render loop that allows us to draw on the AR view.
const onXRFrame = (time, frame) => {
  // Queue up the next draw request.
  session.requestAnimationFrame(onXRFrame);

  // Bind the graphics framebuffer to the baseLayer's framebuffer
  gl.bindFramebuffer(gl.FRAMEBUFFER, session.renderState.baseLayer.framebuffer)

  // Retrieve the pose of the device.
  // XRFrame.getViewerPose can return null while the session attempts to establish tracking.
  const pose = frame.getViewerPose(referenceSpace);
  if (pose) {
    // In mobile AR, we only have one view.
    const view = pose.views[0];

    const viewport = session.renderState.baseLayer.getViewport(view);
    renderer.setSize(viewport.width, viewport.height)

    // Use the view's transform matrix and projection matrix to configure the THREE.camera.
    camera.matrix.fromArray(view.transform.matrix)
    camera.projectionMatrix.fromArray(view.projectionMatrix);
    camera.updateMatrixWorld(true);

    // Render the scene with THREE.WebGLRenderer.
    renderer.render(scene, camera)
  }
}
session.requestAnimationFrame(onXRFrame);

Hello WebXR 실행

기기에서 WebXR 파일로 이동합니다. 모든 측면에서 색상이 지정된 큐브를 볼 수 있어야 합니다.

히트 테스트 추가

AR 세계와 상호작용하는 일반적인 방법은 광선과 실제 도형 간의 교차점을 찾는 히트 테스트를 사용하는 것입니다. Hello WebXR에서는 히트 테스트를 사용하여 가상 세계에 해바라기를 배치합니다.

데모 큐브 삭제

조명이 없는 큐브를 삭제하고 조명이 포함된 장면으로 바꿉니다.

const scene = new THREE.Scene();

const directionalLight = new THREE.DirectionalLight(0xffffff, 0.3);
directionalLight.position.set(10, 15, 10);
scene.add(directionalLight);

hit-test 기능 사용

히트 테스트 기능을 초기화하려면 hit-test 기능으로 세션을 요청합니다. 이전 requestSession() 프래그먼트를 찾아 hit-test를 추가합니다.

const session = await navigator.xr.requestSession("immersive-ar", {requiredFeatures: ['hit-test']});

모델 로더 추가

현재 장면에는 색상이 지정된 큐브만 포함되어 있습니다. 환경을 더 흥미롭게 만들려면 GLTF 모델을 로드할 수 있는 모델 로더를 추가합니다.

문서의 <head> 태그에 three.js의 GLTFLoader를 추가합니다.

<!-- three.js -->
<script src="https://unpkg.com/three@0.126.0/build/three.js"></script>

<script src="https://unpkg.com/three@0.126.0/examples/js/loaders/GLTFLoader.js"></script>

GLTF 모델 로드

이전 단계의 모델 로더를 사용하여 웹에서 타겟팅 레티클과 해바라기를 로드합니다.

onXRFrame 위에 다음 코드를 추가합니다.

const loader = new THREE.GLTFLoader();
let reticle;
loader.load("https://immersive-web.github.io/webxr-samples/media/gltf/reticle/reticle.gltf", function(gltf) {
  reticle = gltf.scene;
  reticle.visible = false;
  scene.add(reticle);
})

let flower;
loader.load("https://immersive-web.github.io/webxr-samples/media/gltf/sunflower/sunflower.gltf", function(gltf) {
  flower = gltf.scene;
});

// Create a render loop that allows us to draw on the AR view.
const onXRFrame = (time, frame) => {

히트 테스트 소스 만들기

실제 물체와의 교차를 계산하려면 XRSession.requestHitTestSource()를 사용하여 XRHitTestSource를 만듭니다. 히트 테스트에 사용되는 광선은 viewer 참조 공간을 원점으로 합니다. 즉, 히트 테스트가 뷰포트 중앙에서 실행됩니다.

히트 테스트 소스를 만들려면 local 참조 공간을 만든 후 다음 코드를 추가합니다.

// A 'local' reference space has a native origin that is located
// near the viewer's position at the time the session was created.
const referenceSpace = await session.requestReferenceSpace('local');

// Create another XRReferenceSpace that has the viewer as the origin.
const viewerSpace = await session.requestReferenceSpace('viewer');
// Perform hit testing using the viewer as origin.
const hitTestSource = await session.requestHitTestSource({ space: viewerSpace });

타겟팅 레티클 그리기

해바라기가 배치될 위치를 명확하게 하려면 장면에 타겟팅 레티클을 추가합니다. 이 레티클은 실제 표면에 붙어 있는 것처럼 표시되며 해바라기가 고정될 위치를 나타냅니다.

XRFrame.getHitTestResultsXRHitTestResult 배열을 반환하고 실제 도형과의 교차를 노출합니다. 이러한 교차점을 사용하여 모든 프레임에 타겟팅 레티클을 배치합니다.

camera.projectionMatrix.fromArray(view.projectionMatrix);
camera.updateMatrixWorld(true);

const hitTestResults = frame.getHitTestResults(hitTestSource);
if (hitTestResults.length > 0 && reticle) {
  const hitPose = hitTestResults[0].getPose(referenceSpace);
  reticle.visible = true;
  reticle.position.set(hitPose.transform.position.x, hitPose.transform.position.y, hitPose.transform.position.z)
  reticle.updateMatrixWorld(true);
}

탭 시 상호작용 추가

XRSession은 사용자가 기본 작업을 완료하면 select 이벤트를 수신합니다. AR 세션에서는 화면을 탭하는 것에 해당합니다.

초기화 중에 다음 코드를 추가하여 사용자가 화면을 탭할 때 새 해바라기가 표시되도록 합니다.

let flower;
loader.load("https://immersive-web.github.io/webxr-samples/media/gltf/sunflower/sunflower.gltf", function(gltf) {
  flower = gltf.scene;
});

session.addEventListener("select", (event) => {
  if (flower) {
    const clone = flower.clone();
    clone.position.copy(reticle.position);
    scene.add(clone);
  }
});

히트 테스트 테스트

휴대기기를 사용하여 페이지로 이동합니다. WebXR이 환경을 이해하면 실제 표면에 레티클이 표시됩니다. 화면을 탭하여 해바라기를 배치합니다. 해바라기는 모든 방향에서 볼 수 있습니다.

다음 단계