日々是好日~every day is a good day~

日常の中の非日常の備忘録

【HTML】ARっぽいHTMLのひな型

久しぶりにARっぽいものを作りたくなりまして…

ARっていろいろ難しいことをやってる感じですが 背景がカメラ映像だと十分ARっぽいし その手前にエフェクトを表示するレイヤーを置いて そのまた手前に3Dオブジェクトを置いたら何だか楽しそう じゃないですか?

こういう仕組みは思いつくのですが 実際これをどうコードで書いたらいいのか分からず…困った時のGemini先生

何度か質問を繰り返して Gemini先生が考えてくれたHTMLとJavaScriptがこちら

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://aframe.io/releases/1.5.0/aframe.min.js"></script>
    <script src="https://cdn.jsdelivr.net/gh/donmccurdy/aframe-extras@v6.1.1/dist/aframe-extras.min.js"></script>
    <script src="https://unpkg.com/aframe-particle-system-component@1.0.x/dist/aframe-particle-system-component.min.js"></script>
    <style>
      /* すべての要素を画面全体に固定 */
      #camera-view,
      #effect-container,
      #aframe-container {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
      }
      /* 1. 一番奥:カメラ映像 */
      #camera-view {
        object-fit: cover;
        z-index: 1;
      }
      /* 2. 真ん中:エフェクト */
      #effect-container {
        position: fixed;
        left: 0;
        width: 100%;
        height: 80%; /* 高さを画面の80%に */
        top: auto; /* topの固定を解除 */
        bottom: 0; /* 画面の一番下に配置 */
        background-color: transparent;
        z-index: 2;
        pointer-events: none; /* エフェクト越しにA-Frameをクリックできるようにする */
      }
      canvas {
        display: block;
        width: 100%;
        height: 100%;
      }
      /* 3. 一番手前:A-Frameの3Dオブジェクト */
      #aframe-container {
        z-index: 3;
      }
    </style>
  </head>

  <body>
    <video id="camera-view" autoplay playsinline muted></video>
    <div id="effect-container"></div>
    <div id="aframe-container">
      <a-scene embedded xr-mode-ui="enabled: false" renderer="alpha: true;">
        <a-assets>
          <a-asset-item
            id="unity-chan"
            src="unity-chanのURL"
          ></a-asset-item>
        </a-assets>
        <a-entity
          animation-mixer
          gltf-model="#unity-chan"
          position="0 0 -3"
          scale="1.5 1.5 1.5"
        ></a-entity>
      </a-scene>
    </div>
    <script defer type="module">
      // 1. エフェクトの起動
      import { Fireworks } from "https://cdn.skypack.dev/fireworks-js";

      const container = document.getElementById("effect-container");
      const fireworks = new Fireworks(container, {
        // 背景を透明にするための設定(花火ライブラリ側の背景をクリアにする)
        background: "transparent",
      });
      fireworks.start();

      // 2. スマホの背面カメラを起動してvideoタグに流す
      const video = document.getElementById("camera-view");

      if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
        // 背面カメラ(environment)を指定
        navigator.mediaDevices
          .getUserMedia({
            video: { facingMode: "environment" },
            audio: false,
          })
          .then((stream) => {
            video.srcObject = stream;
          })
          .catch((error) => {
            console.error("カメラの起動に敗しました:", error);
          });
      }
    </script>
  </body>
</html>

スマホでWebページを開くと

エフェクトはFireworksという既存のものを使いました
この部分(scriptの1.エフェクトの起動の箇所)を以下のように変更すると

      const container = document.getElementById("effect-container");
      const canvas = document.createElement("canvas");
      const ctx = canvas.getContext("2d");
      container.appendChild(canvas);

      // 画面サイズにCanvasを合わせる
      function resizeCanvas() {
        canvas.width = container.clientWidth;
        canvas.height = container.clientHeight;
      }
      resizeCanvas();
      window.addEventListener("resize", resizeCanvas);

      const particles = [];

      // パーティクルクラスの定義
      class Particle {
        constructor(x, y) {
          this.x = x;
          this.y = y;
          // 上方向(画面下部から上)への初速
          this.radius = Math.random() * 10 + 5;
          this.vx = (Math.random() - 0.5) * 2;
          this.vy = -Math.random() * 4 - 2;
          this.alpha = 1;
          this.color = `hsl(${Math.random() * 360}, 100%, 60%)`; // ランダムな色
        }

        update() {
          this.x += this.vx;
          this.y += this.vy;
          this.vy += 0.05; // 擬似的な重力
          this.alpha -= 0.01; // 徐々に透明にする
        }

        draw() {
          ctx.save();
          ctx.globalAlpha = this.alpha;
          ctx.beginPath();
          ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
          ctx.fillStyle = this.color;
          ctx.fill();
          ctx.restore();
        }
      }

      // アニメーションループ
      function animate() {
        // 軌跡を残さず透明にクリア
        ctx.clearRect(0, 0, canvas.width, canvas.height);

        // 一定確率で画面下部から新しいパーティクルを生成
        if (Math.random() < 0.3) {
          // 画面の横幅ランダム、高さは一番下(Canvasの最下部)
          particles.push(
            new Particle(Math.random() * canvas.width, canvas.height)
          );
        }

        // パーティクルの更新と描画
        for (let i = particles.length - 1; i >= 0; i--) {
          const p = particles[i];
          p.update();
          p.draw();

          // 透明になったら配列から削除
          if (p.alpha <= 0) {
            particles.splice(i, 1);
          }
        }
        requestAnimationFrame(animate);
      }
      // アニメーション開始
      animate();


球が噴き上がるようなエフェクトになります
ちなみに このスクリプトもGemini先生が考えてくれました
エフェクト部分や3Dオブジェクト部分を変更するだけでARっぽい画像がいろいろできますね
応用できそう(嬉)
それにしてもGemini先生優秀です 頼りになります