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>

3.js 초기화

시작 버튼을 누를 때는 아무 일도 일어나지 않습니다. 3D 환경을 설정하려면 렌더링 라이브러리를 사용하여 장면을 표시할 수 있습니다.

이 예에서는 WebGL 렌더기를 제공하는 자바스크립트 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);

3.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이 환경에 관한 이해를 얻으면 레티클이 실제 표면에 표시되어야 합니다. 화면을 탭하여 해바라기의 옆면을 모두 볼 수 있습니다.

다음 단계