본문 바로가기
chatGPT와 인공지능AI

chatGPT 가 만든 증강현실

by Cray Fall 2025. 5. 28.

AR 증강현실은 아직 접해본 적이 좀 어렵게 느껴졌는데요.
AI 코딩으로 샘플코드를 짜달라니 세상에나 순삭이네요.

AR은 현실공간과 가상공간을 혼합한 서비스인데요.
스마트폰 웹브라우저(크롬이나 기본 브라우저)에서 지원이 됩니다.

사진의 빨간 상자는 실제 존재하는 사물이 아니라, 터치하여 배치한 가상 물체인데요.
녹색 원은 스마트폰을 들고 카메라를 움직이면 배치할 수 있는 상황에 그 자리가 원으로 표시됩니다.

책상 위에 이렇게 배치할 수도 있습니다.

증강현실은 반드시 https 주소를 필요로 합니다.
그러니 요새 https 주소가 지원되는 저렴한 카페 24 웹호스팅으로 테스트가 가능한데요.

웹호스팅은 이 글의 범주가 아니니 넘어가고,
시도했던 요청문구, 그리고 완성된 소스, 해당 기능이 적용된 크레이의 URL 주소만 공개하겠습니다. 

요청문구는 사실 2개 뿐이라 대단할 것도 없습니다.
chatGPT 에서 첫번째 요청은 아래와 같은데요.

 

웹페이지에서 AR 공간에 물체를 놓는 샘플코드를 만들어줘

결과 HTML 소스를 서버에 올리고 접속해보니 START AR 버튼이 화면에 표시되고

버튼을 누르니 웹페이지를 통해 카메라로 실내공간이 보이는 상황이었습니다.
흥분한 마음을 가라 앉히지 못하고 공간 아무데나 터치해 보았는데,
'응? 아무것도 안 나오는데?'
알고보니 현재 스마트폰 위치에 큐브가 생기더라구요. 카메라를 들고 뒤로 몸을 이동하니 커다란 큐브가 둥둥 떠 있었습니다.

친절한 chatGPT 창에는 아래와 같은 안내 가이드가 적혀 있었습니다.

그래서 바로 요청을 했지요.

실내 공간 배치하는 방식으로 바꿔줘

다시 만들어준 코드를 서버에 올리고 테스트해보니,
처음에는 아무 반응도 없는 듯 하다가 조금 지나면 바닥에 이런 동그란 가이드가 생깁니다.

그리고 화면을 터치하면 상자가 짜잔 하고 나타나더군요.
연속해서 배치할 수 있는데 가까운 거리는 크게, 먼 거리는 작게 입체적으로 보여주는게 상당히 신기했습니다.

해당 기능은 아래 크레이의 홈페이지에서 보실 수 있습니다.
스마트폰에서 접속하시면 테스트해보실 수 있는데요.
단순한 샘플 코드지만 신선한 경험이 되실 겁니다.

https://cray7.mycafe24.com/AR1/

소스코드는 아래와 같습니다.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>WebXR AR Indoor Placement</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script type="module">
      import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.159.0/build/three.module.js';
      import { ARButton } from 'https://cdn.jsdelivr.net/npm/three@0.159.0/examples/jsm/webxr/ARButton.js';

      let camera, scene, renderer;
      let controller;
      let reticle;  // 배치 가이드용 링
      let hitTestSource = null;
      let hitTestSourceRequested = false;

      init();

      function init() {
        scene = new THREE.Scene();
        camera = new THREE.PerspectiveCamera();

        const light = new THREE.HemisphereLight(0xffffff, 0xbbbbff, 1);
        light.position.set(0.5, 1, 0.25);
        scene.add(light);

        renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.xr.enabled = true;
        document.body.appendChild(renderer.domElement);

        document.body.appendChild(ARButton.createButton(renderer, {
          requiredFeatures: ['hit-test']
        }));

        controller = renderer.xr.getController(0);
        controller.addEventListener('select', onSelect);
        scene.add(controller);

        // 배치 가이드 링 (Reticle)
        const ringGeometry = new THREE.RingGeometry(0.1, 0.15, 32).rotateX(-Math.PI / 2);
        const ringMaterial = new THREE.MeshBasicMaterial({ color: 0x0fff00 });
        reticle = new THREE.Mesh(ringGeometry, ringMaterial);
        reticle.matrixAutoUpdate = false;
        reticle.visible = false;
        scene.add(reticle);

        renderer.setAnimationLoop(render);
      }

      function onSelect() {
        if (reticle.visible) {
          const geometry = new THREE.BoxGeometry(0.1, 0.1, 0.1);
          const material = new THREE.MeshPhongMaterial({ color: 0xff0000 });
          const box = new THREE.Mesh(geometry, material);
          box.position.setFromMatrixPosition(reticle.matrix);
          scene.add(box);
        }
      }

      function render(timestamp, frame) {
        if (frame) {
          const referenceSpace = renderer.xr.getReferenceSpace();
          const session = renderer.xr.getSession();

          if (!hitTestSourceRequested) {
            session.requestReferenceSpace('viewer').then((viewerSpace) => {
              session.requestHitTestSource({ space: viewerSpace }).then((source) => {
                hitTestSource = source;
              });
            });
            session.addEventListener('end', () => {
              hitTestSourceRequested = false;
              hitTestSource = null;
            });
            hitTestSourceRequested = true;
          }

          if (hitTestSource) {
            const hitTestResults = frame.getHitTestResults(hitTestSource);
            if (hitTestResults.length > 0) {
              const hit = hitTestResults[0];
              const pose = hit.getPose(referenceSpace);
              reticle.visible = true;
              reticle.matrix.fromArray(pose.transform.matrix);
            } else {
              reticle.visible = false;
            }
          }
        }

        renderer.render(scene, camera);
      }
    </script>
  </head>
  <body style="margin: 0; overflow: hidden; background-color: #000;"></body>
</html>

흥미로운 정보가 되셨나요?
오늘도 방문 주셔서 감사합니다!


이어서 바로 추가 시도해본 우주비행사 배치
아래 URL에서 확인 가능합니다. ( 소스가 1개라 그냥 소스보기하면 보여요 )

https://cray7.mycafe24.com/AR1/2.html