first
This commit is contained in:
213
book/web/01_three.md
Normal file
213
book/web/01_three.md
Normal file
@ -0,0 +1,213 @@
|
||||
# three.js
|
||||
|
||||
reactの`tsx`で書きます。
|
||||
|
||||
```sh
|
||||
$ npx create-react-app galaxy --template typescript
|
||||
```
|
||||
|
||||
これで準備はできましたが、設定ファイルを見てみます。
|
||||
|
||||
> tsconfig.json
|
||||
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
`es5`, `react-jsx`を使っているようです。
|
||||
|
||||
buildなどはwebpackではなく`react-scripts`ですね。
|
||||
|
||||
> package.json
|
||||
|
||||
```json
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
}
|
||||
```
|
||||
|
||||
> src/App.tsx
|
||||
|
||||
```ts
|
||||
import React from 'react';
|
||||
import './App.css';
|
||||
import { ThreeFiberGalaxy } from './pages/galaxy';
|
||||
|
||||
function App() {
|
||||
return <ThreeFiberGalaxy />
|
||||
}
|
||||
|
||||
export default App;
|
||||
```
|
||||
|
||||
> src/pages/galaxy.tsx
|
||||
|
||||
```ts
|
||||
// https://gist.github.com/artokun/fb7f0c68a01ba5d9813abb3ccce254c4
|
||||
|
||||
import * as THREE from 'three'
|
||||
import { Points, useGLTF } from '@react-three/drei'
|
||||
import { GLTF } from 'three-stdlib'
|
||||
import { useFrame, Canvas } from '@react-three/fiber'
|
||||
import { useMemo, useRef } from 'react'
|
||||
import { EffectComposer, SelectiveBloom } from '@react-three/postprocessing'
|
||||
|
||||
type GLTFResult = GLTF & {
|
||||
nodes: {
|
||||
Object_2: THREE.Mesh
|
||||
}
|
||||
materials: {
|
||||
['Scene_-_Root']: THREE.PointsMaterial
|
||||
}
|
||||
}
|
||||
|
||||
export function Galaxy(props: JSX.IntrinsicElements['group']) {
|
||||
const ref = useRef<THREE.Group>(null!)
|
||||
const galaxyCenterLightRef = useRef<THREE.PointLight>(null!)
|
||||
const { nodes } = useGLTF('./models/galaxy.glb') as GLTFResult
|
||||
const [positions, colors] = useMemo(() => {
|
||||
nodes.Object_2.geometry.center()
|
||||
const positions = new Float32Array(
|
||||
nodes.Object_2.geometry.attributes.position.array.buffer
|
||||
)
|
||||
const colors = new Float32Array(positions.length)
|
||||
|
||||
const getDistanceToCenter = (x: number, y: number, z: number) =>
|
||||
Math.sqrt(x * x + y * y + z * z)
|
||||
|
||||
// make colors closer to 0,0,0 be more reddish and colors further away be more blueish
|
||||
const color = new THREE.Color()
|
||||
for (let i = 0; i < positions.length; i += 3) {
|
||||
const x = positions[i]
|
||||
const y = positions[i + 1]
|
||||
const z = positions[i + 2]
|
||||
const distanceToCenter = getDistanceToCenter(x, y, z)
|
||||
const normalizedDistanceToCenter = distanceToCenter / 100
|
||||
|
||||
// make colors closer to 0,0,0 be more reddish and colors further away be more blueish (do not use hsl)
|
||||
// color.setHSL(
|
||||
// (0.15 * (0.21 + Math.cos(distanceToCenter * 0.02))) / 2,
|
||||
// 0.75,
|
||||
// 0.6
|
||||
// )
|
||||
color.setRGB(
|
||||
Math.cos(normalizedDistanceToCenter),
|
||||
THREE.MathUtils.randFloat(0, 0.8),
|
||||
Math.sin(normalizedDistanceToCenter)
|
||||
)
|
||||
color.toArray(colors, i)
|
||||
}
|
||||
|
||||
return [positions, colors]
|
||||
}, [nodes])
|
||||
//const starTexture = useLoader(THREE.TextureLoader, '/star.png')
|
||||
|
||||
// slowly rotate the galaxy
|
||||
useFrame(({ clock }) => {
|
||||
ref.current.rotation.z = clock.getElapsedTime() / 5
|
||||
// zoom in and out
|
||||
// ref.current.scale.setScalar(Math.sin(clock.getElapsedTime() / 2) + 1.5)
|
||||
})
|
||||
|
||||
// make particles glow
|
||||
|
||||
return (
|
||||
<group {...props} dispose={null} ref={ref}>
|
||||
<pointLight
|
||||
position={[0, 0, 0]}
|
||||
ref={galaxyCenterLightRef}
|
||||
intensity={0.5}
|
||||
/>
|
||||
<Points scale={0.05} positions={positions} colors={colors}>
|
||||
<pointsMaterial
|
||||
//map={starTexture}
|
||||
transparent
|
||||
depthWrite={false}
|
||||
vertexColors
|
||||
opacity={0.4}
|
||||
depthTest
|
||||
size={0.01}
|
||||
/>
|
||||
</Points>
|
||||
<EffectComposer autoClear={false}>
|
||||
<SelectiveBloom
|
||||
intensity={2}
|
||||
luminanceThreshold={0.001}
|
||||
luminanceSmoothing={0.225}
|
||||
lights={[galaxyCenterLightRef]}
|
||||
/>
|
||||
</EffectComposer>
|
||||
</group>
|
||||
)
|
||||
}
|
||||
|
||||
useGLTF.preload('./models/galaxy.glb')
|
||||
```
|
||||
|
||||
`App.tsx`で読み込むため以下を追記します。
|
||||
|
||||
> src/pages/galaxy.tsx
|
||||
|
||||
```ts
|
||||
export const ThreeFiberGalaxy = () => {
|
||||
return (
|
||||
<Canvas>
|
||||
<ambientLight />
|
||||
<pointLight position={[10, 10, 10]} />
|
||||
<Galaxy position={[0, 0, 0]} />
|
||||
</Canvas>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
`.glb`は[こちら](https://sketchfab.com/3d-models/need-some-space-d6521362b37b48e3a82bce4911409303)からダウンロードして、`public/models/galaxy.glb`に置いてください。
|
||||
|
||||
```sh
|
||||
$ npm run start
|
||||
```
|
||||
|
||||
controlを追加します。
|
||||
|
||||
```sh
|
||||
import { Points, useGLTF, OrbitControls } from '@react-three/drei'
|
||||
|
||||
export const ThreeFiberGalaxy = () => {
|
||||
return (
|
||||
<Canvas>
|
||||
<OrbitControls />
|
||||
<ambientLight />
|
||||
<pointLight position={[10, 10, 10]} />
|
||||
<Galaxy position={[0, 0, 0]} />
|
||||
</Canvas>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
212
book/web/02_three-vrm.md
Normal file
212
book/web/02_three-vrm.md
Normal file
@ -0,0 +1,212 @@
|
||||
# three-vrm
|
||||
|
||||
今回は`react-three-fiber`と`three-vrm-animation`を使います。
|
||||
|
||||
react-three-fiberはsceneなどを自動でやってくれて、コードもシンプルになります。
|
||||
|
||||
> `react-three-fiber`で書く場合に`.vrma`の動きがおかしくなりました。私の環境では`unity + vrm 1.0`でexportしたものを使うと正常に動きました。
|
||||
|
||||
```sh
|
||||
$ npx create-react-app vrm-model --template typescript
|
||||
$ npm i
|
||||
$ npm run start
|
||||
```
|
||||
|
||||
> src/pages/vrm-model.tsx
|
||||
|
||||
```ts
|
||||
import * as THREE from 'three'
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { OrbitControls } from '@react-three/drei'
|
||||
import { useFrame, Canvas } from '@react-three/fiber';
|
||||
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
|
||||
import { VRM, VRMUtils, VRMLoaderPlugin } from '@pixiv/three-vrm';
|
||||
import { VRMAnimationLoaderPlugin, VRMAnimation, createVRMAnimationClip } from "@pixiv/three-vrm-animation";
|
||||
|
||||
interface ModelProps {
|
||||
url: string
|
||||
url_anim: string
|
||||
}
|
||||
|
||||
const VRMModel: React.FC<ModelProps> = ({ url, url_anim }) => {
|
||||
|
||||
const [vrm, setVrm] = useState<VRM | null>(null);
|
||||
const mixerRef = useRef<THREE.AnimationMixer | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const loader = new GLTFLoader();
|
||||
loader.register((parser) => new VRMLoaderPlugin(parser));
|
||||
loader.register((parser) => new VRMAnimationLoaderPlugin(parser));
|
||||
loader.load(url, (gltf) => {
|
||||
const vrmModel = gltf.userData.vrm as VRM;
|
||||
VRMUtils.removeUnnecessaryJoints(vrmModel.scene);
|
||||
setVrm(vrmModel);
|
||||
const mixer = new THREE.AnimationMixer(vrmModel.scene);
|
||||
mixerRef.current = mixer;
|
||||
loader.load(url_anim, (animGltf) => {
|
||||
const vrmAnimations = animGltf.userData.vrmAnimations as VRMAnimation[];
|
||||
if (vrmAnimations && vrmAnimations.length > 0) {
|
||||
const clip = createVRMAnimationClip(vrmAnimations[0], vrmModel);
|
||||
mixer.clipAction(clip).play();
|
||||
}
|
||||
});
|
||||
});
|
||||
}, [url, url_anim]);
|
||||
|
||||
useFrame((state, delta) => {
|
||||
if (mixerRef.current) mixerRef.current.update(delta);
|
||||
if (vrm) vrm.update(delta);
|
||||
});
|
||||
|
||||
return vrm ? <primitive object={vrm.scene} /> : null;
|
||||
};
|
||||
|
||||
export const VRMModelCanvas = () => {
|
||||
return (
|
||||
<div style={{ height: '100vh', width: '100vw' }}>
|
||||
|
||||
<Canvas
|
||||
shadows
|
||||
gl={{
|
||||
//toneMapping: THREE.ACESFilmicToneMapping,
|
||||
//toneMapping: THREE.ReinhardToneMapping,
|
||||
toneMapping: THREE.NeutralToneMapping,
|
||||
toneMappingExposure: 1.5,
|
||||
alpha: true,
|
||||
powerPreference: "high-performance",
|
||||
antialias: true,
|
||||
//stencil: false,
|
||||
//depth: false
|
||||
}}
|
||||
camera={{ position: [1, 1, 1] }}>
|
||||
|
||||
<directionalLight
|
||||
color="white"
|
||||
castShadow
|
||||
position={[0, 10, 0]}
|
||||
intensity={1.5}
|
||||
shadow-mapSize={[1024, 1024]}/>
|
||||
|
||||
<OrbitControls />
|
||||
<ambientLight intensity={1} />
|
||||
<pointLight position={[10, 10, 10]} />
|
||||
|
||||
<VRMModel url="./models/default.vrm" url_anim="./models/default.vrma" />
|
||||
|
||||
</Canvas>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default VRMModelCanvas;
|
||||
```
|
||||
|
||||
重要な箇所は以下のポイントです。
|
||||
|
||||
```html
|
||||
<VRMModel url="./models/default.vrm" url_anim="./models/default.vrma" />
|
||||
```
|
||||
|
||||
ファイルはこの場所から読み込みますので、ダウンロードする場合は[.vrm](https://vroid.pixiv.help/hc/ja/articles/31627266179865-%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB%E3%83%A2%E3%83%87%E3%83%AB%E3%81%AE%E5%88%A9%E7%94%A8%E6%96%B9%E6%B3%95%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6%E7%9F%A5%E3%82%8A%E3%81%9F%E3%81%84), [.vrma](https://booth.pm/ja/items/5512385)をここに置きます。
|
||||
|
||||
> src/App.tsx
|
||||
|
||||
```ts
|
||||
import React from 'react'
|
||||
import VRMModelCanvas from './pages/vrm_model'
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<VRMModelCanvas/>
|
||||
)
|
||||
}
|
||||
|
||||
export default App;
|
||||
```
|
||||
|
||||
> package.json
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "vrm-model",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@pixiv/three-vrm": "^3.0.0",
|
||||
"@pixiv/three-vrm-animation": "^3.0.0",
|
||||
"@react-three/drei": "^9.109.2",
|
||||
"@react-three/fiber": "^8.16.8",
|
||||
"@react-three/postprocessing": "^2.16.2",
|
||||
"@testing-library/jest-dom": "^5.17.0",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/node": "^16.18.104",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/three": "^0.167.1",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-scripts": "5.0.1",
|
||||
"three": "^0.167.1",
|
||||
"three-stdlib": "^2.30.5",
|
||||
"typescript": "^4.9.5",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> tsconfig.json
|
||||
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
13
book/web/README.md
Normal file
13
book/web/README.md
Normal file
@ -0,0 +1,13 @@
|
||||
# web
|
||||
|
||||
webで動くゲームを開発する技術を紹介します。
|
||||
|
||||
3Dモデルには様々な形式があります。ここでは`.glb`, `.vrm`を扱います。
|
||||
|
||||
これらをwebに表示することができます。例えば、[tyrano builder](https://b.tyrano.jp/)と組み合わせノベルゲームを作成できます。
|
||||
|
||||
|url|body|
|
||||
|---|---|
|
||||
|https://github.com/mrdoob/three.js/|3Dモデルをwebに表示|
|
||||
|https://github.com/pixiv/three-vrm/|vrmをthree.jsで扱う|
|
||||
|
Reference in New Issue
Block a user