🔒 サンドボックス内で実行中 ⛶ 全画面で遊ぶ
HTML / CSS / JS
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>King Ghidorah 3D</title>
  <style>
    body {
      margin: 0;
      overflow: hidden;
      background-color: #050505; /* 暗闇の背景 */
      color: #fff;
      font-family: 'Courier New', Courier, monospace;
      user-select: none;
    }
    #canvas-container {
      width: 100vw;
      height: 100vh;
      display: block;
      position: absolute;
      top: 0;
      left: 0;
      z-index: 1;
    }
    #ui {
      position: absolute;
      bottom: 40px;
      left: 40px;
      pointer-events: none;
      z-index: 10;
    }
    h1 {
      margin: 0;
      font-size: 3rem;
      letter-spacing: 12px;
      color: #ffd700;
      text-shadow: 0 0 20px #ffaa00;
      text-transform: uppercase;
    }
    p {
      margin: 10px 0 0 0;
      font-size: 1rem;
      letter-spacing: 6px;
      color: #aaaaaa;
    }
    #controls-hint {
      position: absolute;
      top: 20px;
      right: 20px;
      font-size: 0.8rem;
      color: #888;
      z-index: 10;
      text-align: right;
      letter-spacing: 1px;
    }
  </style>

  <!-- Three.jsとアドオン -->
  <script type="importmap">
    {
      "imports": {
        "three": "https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js",
        "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/"
      }
    }
  </script>
</head>
<body>
  
  <div id="ui">
    <h1>GHIDORAH</h1>
    <p>KING OF THE MONSTERS</p>
  </div>

  <div id="controls-hint">
    [DRAG] ROTATE<br>
    [SCROLL] ZOOM
  </div>

  <div id="canvas-container"></div>

  <script type="module">
    import * as THREE from 'three';
    import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
    import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
    import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
    import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';

    let scene, camera, renderer, composer;
    let ghidorah;
    const clock = new THREE.Clock();

    // ジオメトリの頂点をランダムに揺らしてゴツゴツさせる関数
    function roughGeometry(geo, strength = 0.5) {
        const pos = geo.attributes.position;
        for(let i=0; i<pos.count; i++) {
            pos.setXYZ(
                i,
                pos.getX(i) + (Math.random()-0.5)*strength,
                pos.getY(i) + (Math.random()-0.5)*strength,
                pos.getZ(i) + (Math.random()-0.5)*strength
            );
        }
        geo.computeVertexNormals();
        return geo;
    }

    // 鱗のような凹凸を作るためのプロシージャルノイズテクスチャ
    function createBumpMap() {
        const canvas = document.createElement('canvas');
        canvas.width = 512;
        canvas.height = 512;
        const ctx = canvas.getContext('2d');
        const imgData = ctx.createImageData(512, 512);
        for(let i=0; i<imgData.data.length; i+=4) {
            // 細かいランダムノイズ
            const v = Math.random() * 255;
            imgData.data[i] = v;
            imgData.data[i+1] = v;
            imgData.data[i+2] = v;
            imgData.data[i+3] = 255;
        }
        ctx.putImageData(imgData, 0, 0);
        const tex = new THREE.CanvasTexture(canvas);
        tex.wrapS = THREE.RepeatWrapping;
        tex.wrapT = THREE.RepeatWrapping;
        tex.repeat.set(10, 10);
        return tex;
    }

    // 共通の黄金マテリアル(重厚な金属質+ゴツゴツした鱗感)
    const goldMat = new THREE.MeshStandardMaterial({
        color: 0xffd700,
        emissive: 0x331a00, // 暗所でのシルエット強調
        metalness: 1.0,     
        roughness: 0.35,     
        bumpMap: createBumpMap(),
        bumpScale: 0.15,    // 凹凸の強さ
        flatShading: true,  // 【追加】ポリゴンの面を際立たせてゴツゴツさせる
        side: THREE.DoubleSide
    });

    function init() {
      const container = document.getElementById('canvas-container');

      // 1. シーンとカメラの設定
      scene = new THREE.Scene();
      scene.fog = new THREE.FogExp2(0x050505, 0.002);

      camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 1500);
      // さらに全身が収まるようにカメラを調整
      camera.position.set(0, 80, 350);

      // 2. レンダラーの設定
      renderer = new THREE.WebGLRenderer({ antialias: true, alpha: false });
      renderer.setPixelRatio(window.devicePixelRatio);
      renderer.setSize(window.innerWidth, window.innerHeight);
      renderer.toneMapping = THREE.ACESFilmicToneMapping;
      renderer.toneMappingExposure = 1.2;
      container.appendChild(renderer.domElement);

      // 3. ライティング(金属の質感を出すための多方向からの光)
      const ambientLight = new THREE.AmbientLight(0x404040, 1.0);
      scene.add(ambientLight);

      const dirLight1 = new THREE.DirectionalLight(0xffeedd, 3.5);
      dirLight1.position.set(100, 200, 100);
      scene.add(dirLight1);

      const dirLight2 = new THREE.DirectionalLight(0xffaa55, 2.0);
      dirLight2.position.set(-100, -50, -100);
      scene.add(dirLight2);
      
      const rimLight = new THREE.DirectionalLight(0xffeeaa, 2.0);
      rimLight.position.set(0, 100, -200); // 背後からのリムライトでシルエットを強調
      scene.add(rimLight);

      // 4. ポストプロセッシング(控えめなブルーム)
      const renderScene = new RenderPass(scene, camera);
      const bloomPass = new UnrealBloomPass(
        new THREE.Vector2(window.innerWidth, window.innerHeight),
        0.8,  // 強さ(控えめに)
        0.4,  // 半径
        0.6   // 閾値(光沢の強い部分だけ光らせる)
      );

      composer = new EffectComposer(renderer);
      composer.addPass(renderScene);
      composer.addPass(bloomPass);

      // 5. コントロール
      const controls = new OrbitControls(camera, renderer.domElement);
      controls.enableDamping = true;
      controls.dampingFactor = 0.05;
      controls.maxDistance = 600;
      controls.target.set(0, 50, 0); // 胴体を中心に

      // 6. ギドラの構築
      ghidorah = new FullGhidorah();
      scene.add(ghidorah.group);

      window.addEventListener('resize', onWindowResize);
    }

    // --- ギドラ全身を管理するクラス ---
    class FullGhidorah {
      constructor() {
        this.group = new THREE.Group();
        this.appendages = []; // 首と尻尾
        this.wings = [];      // 翼
        
        // 全体の基準位置
        this.baseY = 20;
        this.group.position.y = this.baseY;

        this.buildBody();
        this.buildLegs();
        this.buildWings();
        
        // 首 3本 (中央を長く、左右を少し短く曲げる)
        this.appendages.push(new GhidorahAppendage('neck', new THREE.Vector3(0, 25, 10), 0));    // 中央
        this.appendages.push(new GhidorahAppendage('neck', new THREE.Vector3(-12, 15, 0), -1));  // 左
        this.appendages.push(new GhidorahAppendage('neck', new THREE.Vector3(12, 15, 0), 1));    // 右

        // 尻尾 2本
        this.appendages.push(new GhidorahAppendage('tail', new THREE.Vector3(-8, -10, -15), -1));
        this.appendages.push(new GhidorahAppendage('tail', new THREE.Vector3(8, -10, -15), 1));

        this.appendages.forEach(app => this.group.add(app.group));
      }

      // ゴツゴツした胴体(より生物的なフォルムへ)
      buildBody() {
        const bodyGroup = new THREE.Group();
        
        // 胸部(張り出したマッシブな胸、頂点を揺らして岩のようにゴツゴツさせる)
        const chestGeo = roughGeometry(new THREE.IcosahedronGeometry(18, 2), 2.0);
        const chest = new THREE.Mesh(chestGeo, goldMat);
        chest.position.set(0, 15, 5);
        chest.scale.set(1, 0.9, 1.2);
        bodyGroup.add(chest);

        // 腹部〜腰
        const bellyGeo = roughGeometry(new THREE.IcosahedronGeometry(14, 2), 1.5);
        const belly = new THREE.Mesh(bellyGeo, goldMat);
        belly.position.set(0, 0, -5);
        belly.scale.set(0.9, 1.2, 1.0);
        bodyGroup.add(belly);

        // 背中の棘(列状に並べる)
        for(let i=0; i<7; i++) {
            const spikeGeo = new THREE.ConeGeometry(2.5, 18, 4);
            const spike = new THREE.Mesh(spikeGeo, goldMat);
            spike.position.set(0, 25 - i*6, -5 - i*3);
            spike.rotation.x = -Math.PI / 4 - (i*0.1);
            bodyGroup.add(spike);
        }

        this.group.add(bodyGroup);
      }

      // 力強い2本の足(関節を意識した構造)
      buildLegs() {
        const legsGroup = new THREE.Group();
        for(let i = 0; i < 2; i++) {
            const isLeft = i === 0;
            const sign = isLeft ? -1 : 1;
            const leg = new THREE.Group();
            leg.position.set(sign * 14, 0, -5);
            
            // 太もも
            const thighGeo = roughGeometry(new THREE.IcosahedronGeometry(9, 1), 1.5);
            const thigh = new THREE.Mesh(thighGeo, goldMat);
            thigh.scale.set(1, 1.8, 1.2);
            thigh.position.set(sign * 2, -10, 5);
            thigh.rotation.x = -0.3;
            leg.add(thigh);
            
            // すね(六角柱を歪ませる)
            const calfGeo = roughGeometry(new THREE.CylinderGeometry(5, 3, 25, 6), 1.0);
            const calf = new THREE.Mesh(calfGeo, goldMat);
            calf.position.set(sign * 2, -30, 0);
            calf.rotation.x = 0.2;
            leg.add(calf);
            
            // 足首・足の甲
            const footGeo = roughGeometry(new THREE.IcosahedronGeometry(6, 1), 1.0);
            const foot = new THREE.Mesh(footGeo, goldMat);
            foot.scale.set(1.2, 0.6, 1.5);
            foot.position.set(sign * 2, -42, 5);
            leg.add(foot);
            
            // 爪
            const clawGeo = new THREE.ConeGeometry(1.5, 10, 4);
            clawGeo.rotateX(Math.PI / 2);
            for(let j=0; j<3; j++) {
                const claw = new THREE.Mesh(clawGeo, goldMat);
                claw.position.set(sign * 2 + (j-1)*4, -42, 12);
                claw.rotation.y = (j-1) * 0.2 * sign;
                leg.add(claw);
            }
            
            legsGroup.add(leg);
        }
        this.group.add(legsGroup);
      }

      // 巨大な翼(ShapeGeometryを用いてコウモリのような皮膜を再現)
      buildWings() {
        this.wingsGroup = new THREE.Group();
        
        // 翼のシルエット定義
        const shape = new THREE.Shape();
        shape.moveTo(0, 0); // 付け根
        shape.lineTo(40, 30); // 腕の第一関節
        shape.lineTo(95, 45); // 翼端1
        shape.quadraticCurveTo(80, 10, 110, -20); // 翼端2
        shape.quadraticCurveTo(70, -5, 80, -50);  // 翼端3
        shape.quadraticCurveTo(45, -10, 40, -60);  // 翼端4
        shape.quadraticCurveTo(20, -15, 0, -30);  // 胴体へ戻る
        shape.lineTo(0, 0);

        const wingGeo = new THREE.ShapeGeometry(shape);
        // 皮膜に立体感をつける(波打たせる)
        const pos = wingGeo.attributes.position;
        for(let j=0; j<pos.count; j++) {
             const x = pos.getX(j);
             const y = pos.getY(j);
             // 大きなうねりに加え、細かいランダムな凹凸を追加してシワっぽくする
             pos.setZ(j, Math.sin(x * 0.1) * 8 + Math.cos(y * 0.1) * 4 + (Math.random()-0.5)*1.5);
        }
        wingGeo.computeVertexNormals();

        for (let i = 0; i < 2; i++) {
            const isLeft = i === 0;
            const sign = isLeft ? -1 : 1;
            const wing = new THREE.Group();
            wing.position.set(sign * 15, 25, -10);

            // 主骨(太い腕、カクカクさせる)
            const armGeo = roughGeometry(new THREE.CylinderGeometry(4, 2, 60, 6), 0.5);
            const arm = new THREE.Mesh(armGeo, goldMat);
            arm.position.set(sign * 25, 15, 0);
            arm.rotation.z = sign * -Math.PI / 3;
            wing.add(arm);

            // 皮膜メッシュ
            const membrane = new THREE.Mesh(wingGeo, goldMat);
            // 左翼の場合はX軸を反転させる
            if (isLeft) {
                membrane.scale.x = -1;
            }
            wing.add(membrane);
            
            wing.userData = { sign: sign, baseRotZ: sign * 0.1, baseRotY: sign * -0.2 };
            wing.rotation.z = wing.userData.baseRotZ;
            wing.rotation.y = wing.userData.baseRotY;

            this.wings.push(wing);
            this.wingsGroup.add(wing);
        }
        this.group.add(this.wingsGroup);
      }

      update(time) {
        // 1. 全体のゆっくりとした上下動(呼吸)
        this.group.position.y = this.baseY + Math.sin(time * 1.5) * 2;
        
        // 2. 翼の羽ばたき
        this.wings.forEach(wing => {
            const flap = Math.sin(time * 2.0);
            wing.rotation.z = wing.userData.baseRotZ + (flap * wing.userData.sign * 0.3);
            wing.rotation.y = wing.userData.baseRotY + (flap * wing.userData.sign * 0.1);
        });

        // 3. 首と尻尾のうねり
        this.appendages.forEach(app => app.update(time));
      }
    }

    // --- 首と尻尾を生成・管理する汎用クラス ---
    class GhidorahAppendage {
      constructor(type, startPos, indexSign) {
        this.group = new THREE.Group();
        this.type = type; 
        this.indexSign = indexSign; 
        this.segmentCount = type === 'neck' ? 50 : 60; 
        
        this.startPos = startPos;
        this.points = [];
        this.basePoints = [];
        
        if (type === 'neck') {
            // 中央は高く、左右は外側に広がる
            const height = indexSign === 0 ? 80 : 60;
            const spread = indexSign * 40;
            const endPos = new THREE.Vector3(spread, height, 40 + Math.abs(indexSign)*10);
            this.basePoints = [
                startPos.clone(),
                new THREE.Vector3(spread * 0.3, height * 0.4, 10),
                new THREE.Vector3(spread * 0.7, height * 0.7, 20),
                endPos.clone()
            ];
        } else {
            // 尻尾
            const endPos = new THREE.Vector3(indexSign * 50, -40, -120);
            this.basePoints = [
                startPos.clone(),
                new THREE.Vector3(indexSign * 15, -20, -40),
                new THREE.Vector3(indexSign * 30, -30, -80),
                endPos.clone()
            ];
        }
        
        this.basePoints.forEach(p => this.points.push(p.clone()));
        this.curve = new THREE.CatmullRomCurve3(this.points);

        // 節の形を滑らかな円柱から、ゴツゴツした多面体(八面体)に変更
        const segmentGeo = new THREE.OctahedronGeometry(4.5, 0); 
        this.instancedMesh = new THREE.InstancedMesh(segmentGeo, goldMat, this.segmentCount);
        
        // 背中の棘(少しランダムに歪ませて有機的に)
        const spikeGeo = roughGeometry(new THREE.ConeGeometry(2, 12, 4), 0.3);
        spikeGeo.translate(0, 6, 0); 
        this.spikeMesh = new THREE.InstancedMesh(spikeGeo, goldMat, this.segmentCount);

        this.group.add(this.instancedMesh);
        this.group.add(this.spikeMesh);

        if (type === 'neck') {
            this.head = this.buildHead();
            this.group.add(this.head);
        } else {
            // 尻尾の先端(トゲ付きメイス)
            const tailTipGroup = new THREE.Group();
            const tipCore = new THREE.Mesh(new THREE.IcosahedronGeometry(4, 1), goldMat);
            tailTipGroup.add(tipCore);
            // 周囲のトゲ
            for(let i=0; i<4; i++) {
                const s = new THREE.Mesh(new THREE.ConeGeometry(1, 8, 4), goldMat);
                s.rotation.x = Math.PI/2;
                s.rotation.z = (Math.PI/2) * i;
                s.position.y = Math.sin((Math.PI/2)*i) * 3;
                s.position.x = Math.cos((Math.PI/2)*i) * 3;
                tailTipGroup.add(s);
            }
            this.head = tailTipGroup;
            this.group.add(this.head);
        }

        this.dummy = new THREE.Object3D();
        this.spikeDummy = new THREE.Object3D();
        this.timeOffset = indexSign * 2.0 + (type === 'tail' ? 5.0 : 0);
      }

      // 竜の頭部(よりシャープで威圧感のあるシルエットに)
      buildHead() {
          const headGroup = new THREE.Group();
          const matGlow = new THREE.MeshBasicMaterial({ color: 0xff1100 }); // 赤い目

          // 頭部全体を少し前に出す
          const offsetZ = 5;

          // 上顎(細長くシャープに)
          const upperJawGeo = new THREE.ConeGeometry(2.0, 16, 6);
          upperJawGeo.rotateX(-Math.PI / 2);
          upperJawGeo.translate(0, 2, offsetZ + 4);
          const upperJaw = new THREE.Mesh(upperJawGeo, goldMat);
          headGroup.add(upperJaw);

          // 下顎
          const lowerJawGeo = new THREE.ConeGeometry(1.2, 14, 6);
          lowerJawGeo.rotateX(-Math.PI / 2 + 0.3); 
          lowerJawGeo.translate(0, -0.5, offsetZ + 3);
          const lowerJaw = new THREE.Mesh(lowerJawGeo, goldMat);
          headGroup.add(lowerJaw);

          // 後頭部・頬
          const skullGeo = new THREE.SphereGeometry(3.5, 8, 8);
          const skull = new THREE.Mesh(skullGeo, goldMat);
          skull.position.set(0, 2, offsetZ - 2);
          headGroup.add(skull);

          // 放射状に伸びる角(王冠のように)
          const hornAngles = [-0.6, -0.3, 0, 0.3, 0.6];
          hornAngles.forEach((angle, idx) => {
              const length = idx === 2 ? 14 : 10; // 中央を長く
              const hornGeo = new THREE.ConeGeometry(1, length, 4);
              hornGeo.rotateX(Math.PI / 2 + 0.4); 
              const horn = new THREE.Mesh(hornGeo, goldMat);
              horn.position.set(angle * 5, 4, offsetZ - 4);
              horn.rotation.y = angle;
              horn.rotation.x -= Math.abs(angle) * 0.5;
              headGroup.add(horn);
          });

          // 目(少し吊り目に配置)
          const eyeGeo = new THREE.SphereGeometry(0.7, 8, 8);
          const eyeL = new THREE.Mesh(eyeGeo, matGlow);
          eyeL.position.set(-1.8, 3.2, offsetZ + 1);
          headGroup.add(eyeL);
          
          const eyeR = new THREE.Mesh(eyeGeo, matGlow);
          eyeR.position.set(1.8, 3.2, offsetZ + 1);
          headGroup.add(eyeR);

          return headGroup;
      }

      update(time) {
        const t = time * 2.0 + this.timeOffset;

        // 制御点を揺らして有機的なうねりを作る
        for(let i = 1; i < this.points.length; i++) {
           const waveAmp = this.type === 'neck' ? 8 : 18; 
           const speedMultiplier = this.type === 'neck' ? 0.8 : 1.2;
           
           this.points[i].x = this.basePoints[i].x + Math.sin(t * speedMultiplier + i) * waveAmp;
           this.points[i].y = this.basePoints[i].y + Math.cos(t * 0.7 * speedMultiplier + i) * waveAmp * 0.5;
           if(i < this.points.length - 1) {
               this.points[i].z = this.basePoints[i].z + Math.sin(t * 1.1 * speedMultiplier + i) * waveAmp;
           }
        }

        const curvePoints = this.curve.getPoints(this.segmentCount - 1);

        for(let i = 0; i < this.segmentCount; i++) {
           const pt = curvePoints[i];
           this.dummy.position.copy(pt);
           this.spikeDummy.position.copy(pt);
           
           if (i < this.segmentCount - 1) {
               this.dummy.lookAt(curvePoints[i+1]);
               this.spikeDummy.lookAt(curvePoints[i+1]);
           }

           // 節ごとにランダムな角度をつけて、鱗が重なり合ったようなゴツゴツ感を出す
           this.dummy.rotateZ(Math.sin(t*0.5 + i*0.1) * 0.2 + (i * 123.45));
           this.dummy.rotateX((i * 45.67)); 
           
           // 太さの変化(滑らかに)
           let scale = 1.0;
           const norm = i / this.segmentCount;
           if (this.type === 'neck') {
               // 根元太い(1.2) -> 中間少し細い(0.8) -> 頭付近戻る(1.0)
               scale = 0.8 + Math.sin(norm * Math.PI) * 0.1 + (1.0 - norm) * 0.5;
           } else {
               scale = 1.3 * (1.0 - norm * 0.7);
           }
           
           this.dummy.scale.set(scale, scale, 1.0); // Z軸(進行方向)は伸ばさない
           this.dummy.updateMatrix();
           this.instancedMesh.setMatrixAt(i, this.dummy.matrix);

           // 棘の配置(背中側)
           this.spikeDummy.rotateX(-Math.PI / 2); 
           this.spikeDummy.translateY(scale * 3.5); 
           const spikeScale = scale * (this.type === 'neck' ? 0.9 : 1.3) * (i % 2 === 0 ? 1 : 0.6); 
           this.spikeDummy.scale.set(spikeScale, spikeScale, spikeScale);
           this.spikeDummy.updateMatrix();
           this.spikeMesh.setMatrixAt(i, this.spikeDummy.matrix);
        }

        this.instancedMesh.instanceMatrix.needsUpdate = true;
        this.spikeMesh.instanceMatrix.needsUpdate = true;

        // 先端パーツの更新
        const lastPt = curvePoints[this.segmentCount - 1];
        const prevPt = curvePoints[this.segmentCount - 2];
        this.head.position.copy(lastPt);
        
        const dir = new THREE.Vector3().subVectors(lastPt, prevPt).normalize();
        this.head.lookAt(lastPt.clone().add(dir));
        
        if (this.type === 'neck') {
            this.head.rotateY(Math.sin(t) * 0.3);
            this.head.rotateZ(Math.cos(t * 0.8) * 0.2);
        }
      }
    }

    function onWindowResize() {
      camera.aspect = window.innerWidth / window.innerHeight;
      camera.updateProjectionMatrix();
      renderer.setSize(window.innerWidth, window.innerHeight);
      composer.setSize(window.innerWidth, window.innerHeight);
    }

    function animate() {
      requestAnimationFrame(animate);
      const time = clock.getElapsedTime();
      
      if (ghidorah) {
          ghidorah.update(time);
      }
      
      composer.render();
    }

    init();
    animate();
  </script>
</body>
</html>

ギドラ

黄金の輝きを表現しました。

🤖 使用したAI
Gemini
👁 18 回閲覧
📅 投稿:2026年7月5日 🔄 更新:2026年8月13日
応援スタンプを押してみよう!