diff --git a/atmosphere/README.md b/atmosphere/README.md new file mode 100644 index 000000000..e69de29bb diff --git a/atmosphere/index.html b/atmosphere/index.html new file mode 100644 index 000000000..a3d638f59 --- /dev/null +++ b/atmosphere/index.html @@ -0,0 +1,15 @@ + + + + + + VRM Animation Preview + + + +
+ + + diff --git a/atmosphere/package.json b/atmosphere/package.json new file mode 100644 index 000000000..1aefc5572 --- /dev/null +++ b/atmosphere/package.json @@ -0,0 +1,26 @@ +{ + "name": "react-vrm-atmosphere", + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@pixiv/three-vrm": "^3.4.4", + "@pixiv/three-vrm-animation": "^3.4.4", + "@react-three/drei": "^10.7.7", + "@react-three/fiber": "^9.4.0", + "@react-three/postprocessing": "^3.0.4", + "@takram/three-atmosphere": "^0.15.1", + "@takram/three-clouds": "^0.5.2", + "react": "^19.0.0-rc.1", + "react-dom": "^19.0.0-rc.1", + "three": "^0.181.2" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.2.1", + "vite": "^5.1.0" + } +} diff --git a/atmosphere/public/ai.vrm b/atmosphere/public/ai.vrm new file mode 100644 index 000000000..d8fb84c00 Binary files /dev/null and b/atmosphere/public/ai.vrm differ diff --git a/atmosphere/public/fly.vrma b/atmosphere/public/fly.vrma new file mode 100644 index 000000000..5cd32d27d Binary files /dev/null and b/atmosphere/public/fly.vrma differ diff --git a/atmosphere/public/idle.vrma b/atmosphere/public/idle.vrma new file mode 100644 index 000000000..fbb400bb5 Binary files /dev/null and b/atmosphere/public/idle.vrma differ diff --git a/atmosphere/public/run.vrma b/atmosphere/public/run.vrma new file mode 100644 index 000000000..242c889c8 Binary files /dev/null and b/atmosphere/public/run.vrma differ diff --git a/atmosphere/src/App.jsx b/atmosphere/src/App.jsx new file mode 100644 index 000000000..3b6c48996 --- /dev/null +++ b/atmosphere/src/App.jsx @@ -0,0 +1,204 @@ +import React, { useEffect, useRef, Suspense } from 'react'; +import { Canvas, useFrame, useLoader, useThree } from '@react-three/fiber'; +import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'; +import { VRMLoaderPlugin, VRMUtils } from '@pixiv/three-vrm'; +import { createVRMAnimationClip, VRMAnimationLoaderPlugin } from '@pixiv/three-vrm-animation'; +import { AnimationMixer, Vector3 } from 'three'; +import { OrbitControls, PerspectiveCamera } from '@react-three/drei'; +import { EffectComposer, ToneMapping } from '@react-three/postprocessing'; +import { ToneMappingMode } from 'postprocessing'; +import * as THREE from 'three'; + +// Takram Libraries +import { AerialPerspective, Atmosphere } from '@takram/three-atmosphere/r3f'; +import { Clouds, CloudLayer } from '@takram/three-clouds/r3f'; + +// --- Constants --- +const VRM_URL = '/ai.vrm'; +const VRMA_URL = '/fly.vrma'; +const EARTH_RADIUS = 6378137; + +// ★追加: 時刻を「2024年6月21日 12:00 (正午)」に固定するためのDateオブジェクト +// 季節によって太陽高度が変わるため、夏至の昼を選んでおくと最も明るくなります。 +const FIXED_DATE = new Date('2024-06-21T12:00:00'); + +// --------------------------------------------------------- +// Scene 1: Atmosphere (奥・リアルスケール・HDR設定) +// --------------------------------------------------------- + +// 明るさ調整用のコンポーネント +function ExposureController() { + const { gl } = useThree(); + useEffect(() => { + // Takramのライブラリは物理単位の光量を扱うため、 + // 露出を劇的に上げる必要があります。 + gl.toneMapping = THREE.NoToneMapping; + gl.toneMappingExposure = 10.0; + }, [gl]); + return null; +} + +function AtmosphereLayer() { + const cameraRef = useRef(); + + return ( + + + + + + {/* 太陽: 真上(正午) + 強度アップ */} + + + {/* ★修正: date={FIXED_DATE} を渡して時間を固定 */} + + + + + + + + + + + {/* 重要: AGXトーンマッピングで、高い露出でも色を綺麗にまとめる */} + + + + + + + ); +} + +function FlyOverCamera() { + useFrame((state) => { + const t = state.clock.getElapsedTime() * 0.05; + const altitude = 2000; + const radius = 5000; + + state.camera.position.x = Math.sin(t) * radius; + state.camera.position.z = Math.cos(t) * radius; + state.camera.position.y = EARTH_RADIUS + altitude; + + const lookAtTarget = new Vector3( + Math.sin(t + 0.1) * radius, + EARTH_RADIUS + altitude, + Math.cos(t + 0.1) * radius + ); + state.camera.lookAt(lookAtTarget); + }); + return null; +} + +// --------------------------------------------------------- +// Scene 2: Avatar (手前・ローカルスケール) +// --------------------------------------------------------- +function VrmCharacter() { + const mixerRef = useRef(null); + const vrmRef = useRef(null); + + const gltf = useLoader(GLTFLoader, VRM_URL, (loader) => { + loader.register((parser) => new VRMLoaderPlugin(parser)); + }); + + const vrma = useLoader(GLTFLoader, VRMA_URL, (loader) => { + loader.register((parser) => new VRMAnimationLoaderPlugin(parser)); + }); + + useEffect(() => { + const vrm = gltf.userData.vrm; + vrmRef.current = vrm; + VRMUtils.removeUnnecessaryJoints(vrm.scene); + vrm.humanoid.resetPose(); + vrm.scene.rotation.y = Math.PI; + + if (vrma.userData.vrmAnimations?.[0]) { + const clip = createVRMAnimationClip(vrma.userData.vrmAnimations[0], vrm); + mixerRef.current = new AnimationMixer(vrm.scene); + mixerRef.current.clipAction(clip).play(); + } + }, [gltf, vrma]); + + useFrame((state, delta) => { + mixerRef.current?.update(delta); + vrmRef.current?.update(delta); + }); + + return ; +} + +function AvatarLayer() { + return ( + + + + {/* キャラのライティングは背景とは独立して、キャラが映える設定にする */} + + + + + + + + + + + ); +} + +// --------------------------------------------------------- +// Main App +// --------------------------------------------------------- +export default function App() { + const layerStyle = { + position: 'absolute', + top: 0, + left: 0, + width: '100%', + height: '100%', + }; + + return ( +
+ + {/* Layer 0: Atmosphere (明るさ補正済み) */} +
+ +
+ + {/* Layer 1: Avatar */} +
+
+ +
+
+ +
+ ); +} diff --git a/atmosphere/src/main.jsx b/atmosphere/src/main.jsx new file mode 100644 index 000000000..1943cc824 --- /dev/null +++ b/atmosphere/src/main.jsx @@ -0,0 +1,9 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App' + +ReactDOM.createRoot(document.getElementById('root')).render( + + + , +) diff --git a/atmosphere/vite.config.ts b/atmosphere/vite.config.ts new file mode 100644 index 000000000..9ffcc6757 --- /dev/null +++ b/atmosphere/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +export default defineConfig({ + plugins: [react()], +})