WEBGLの学習メモ②THREE.jsで雪だるまの作成

1558 回閲覧されました
みなさんこんにちは、WEB制作のエンジニアのjonioです。
今回はネットの記事を見てThree.jsで雪だるまを作ったのですがすぐ忘れる自分への学習メモを残します。
今回でちょっとだけ理解が深まりました。
それでは解説します。
目次 [隠す]
デモ
デモは↓です、横にくるくると回っているだけですがマウスで雪だるまの角度を変えることもできます。
考え方
雪だるまには目・鼻・顔などありますが全てパーツにしてそれぞれを作成して全部を合わせることで雪だるまにします。
パーツにする物を↓に記載しています。
10個のパーツを作らないといけないので大変ですが複雑な物を3Dにするとパーツが増えるので時間が相当かかります。
それでは作成しますが順を追って作成していきます。
帽子の上の作成
帽子の上の作成が理解できれば他は似てくるため帽子の上の作成方法のみ解説します。
帽子→顔→体の順に作成しますがその前に雪だるまが表示されるためにHTMLを↓にします。
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"/> | |
<script src="https://unpkg.com/three@0.140.2/build/three.min.js"></script> | |
<script src="https://unpkg.com/three@0.137.4/examples/js/controls/OrbitControls.js"></script> | |
<title>THREE.js</title> | |
</head> | |
<body> | |
<canvas id="myCanvas"></canvas> | |
<script type="text/javascript" src="js/common.js"></script> | |
</body> | |
</html> |
5行目がThree.jsを読み込むためのCDNです。
6行目がマウスを使って雪だるまの向きを変えるためのJavaScriptの記述を使えるようにするためのCDNです。(向きの変え方は最後に説明します)
10行目で雪だるまを表示する場所を指定してます。
それではJavaScriptで雪だるまを作りますが今からのコードは全てJavaScriptです。
シーン(表示する場所)の設定
コードを↓にします。
window.addEventListener('DOMContentLoaded', init); | |
function init() { | |
const width = 560; | |
const height = 300; | |
const scene = new THREE.Scene(); | |
const hatMaterial = new THREE.MeshLambertMaterial({ color: 0x333333 }) | |
const hat = new THREE.Mesh( | |
new THREE.CylinderGeometry(25, 25, 40, 40), | |
hatMaterial | |
); | |
hat.position.set(0, 50, 0); | |
const snowman = new THREE.Group(); | |
snowman.add(hat); | |
scene.add(snowman); | |
const light = new THREE.DirectionalLight(0xffffff, 0.9); | |
light.position.set(0, 50, 30); | |
scene.add(light); | |
const ambient = new THREE.AmbientLight(0xf8f8ff, 0.9); | |
scene.add(ambient); | |
const camera = new THREE.PerspectiveCamera(70, width / height, 1, 1000); | |
camera.position.set(60, 50, 140); | |
camera.lookAt(scene.position); | |
const renderer = new THREE.WebGLRenderer({ | |
canvas: document.querySelector('#myCanvas'), | |
antialias: true | |
}); | |
renderer.setSize(width, height); | |
renderer.setClearColor(0xe6e6fa); | |
renderer.setPixelRatio(window.devicePixelRatio); | |
function render() { | |
requestAnimationFrame(render); | |
renderer.render(scene, camera); | |
snowman.rotation.y += 0.01; | |
} | |
render(); | |
} |
これで↓と表示されます。
雪だるまを表示するためには表示する場所の設定をしないといけません。
それをシーンと言いますが8行目でシーンの設定をしています。
雪だるまを表示する設定
33行目〜36行目で表示したい内容を表示できるようにしています。
34行目でHTMLの10行目の場所に雪だるまを表示して35行目で物体の輪郭を滑らかにします。
5行目が表示する全体の横の長さの設定で6行目が表示する全体の縦の長さの設定です。
これを38行目で適用しています。
背景色の設定
40行目は背景色の設定でこれで背景色を紫っぽくしています。
Retinaディスプレイの対応
42行目でThree.jsで書いた物をcanvasで表示した時にRetinaディスプレイに対応できるようにします。
雪だるまの回転
47行目で雪だるまが横に回ることができるようにします。
回転する方向はx方向・y方向・z方向とありますが図にすると↓になります。
表示したい内容を表示する
46行目で表示したい内容(今だと帽子)を表示しています。
45行目で一定時間ごとに表示していますがこれで横にくるくる回るのを実現しています。
表示したい内容ですがカメラがないと表示できませんがそれが29行目〜31行目です。
29行目がカメラの種類の設定で30行目がカメラの位置の設定で31行目はカメラが向く方向の設定ですが「.lookAt(scene.position)」と書くとカメラがシーンの方向を向きます。
表示するための大前提の設定が終わったので今から表示したい内容を表示するためのコードです。
帽子の上を作成するためのコード
帽子の上を作りますが表示する物(メッシュ)を作るのにマテリアル(影が付く付かないなどの質感の設定)とジオメトリ(物体の形)の設定が必要になります。
10行目がマテリアルの設定です。
13行目でジオメトリの設定をして12行目〜15行目でメッシュにしています。
16行目で「帽子の上」の表示する位置の設定をしています。
「(0,50,0)」とありますが「(x方向,y方向,z方向)」で値を変えると位置を調節することできます。
18行目の「.Group()」ですが表示する物が1つの場合はいらないのですが今回はパーツを合わせて1つの物にするので必要になります。
表示するパーツですが19行目の「snowman.add(hat);」の「hat」の後ろに「,(セミコロン)」で区切って内容を加えればいいです。(snowman.add(hat,button,eye)みたいな感じ)
20行目で帽子の上をシーンで表示できるようにします。
今回のジオメトリは「MeshLambertMaterial」でこれは陰影が付くジオメトリですが陰影をつけるためにライトが必要になります。
それが22行目〜24行目と26行目〜27行目です。
22行目・26行目でライトの種類の設定です、22行目は特定の方向に光が当たり26行目で表示する物全体に均等に光が当たります。
23行目でライトの位置の設定・24行目・27行目でシーンにライトを追加しています。
残りの部分ですが考え方が似ているので説明しておきたい部分以外はコードだけにします。
他のパーツ
コードを↓にします。
window.addEventListener('DOMContentLoaded', init); | |
function init() { | |
const width = 560; | |
const height = 300; | |
const scene = new THREE.Scene(); | |
const buttonGeometry = new THREE.BoxGeometry(5, 5, 5) | |
const hatMaterial = new THREE.MeshLambertMaterial({ color: 0x333333 }) | |
//ここから追加 | |
const headMaterial = new THREE.MeshLambertMaterial({ color: 0xffffff }) | |
const buttonMaterial = new THREE.MeshLambertMaterial({ color: 0x228b22 }) | |
const eyeMaterial = new THREE.MeshToonMaterial({ color: 0x000000 }); | |
const hat = new THREE.Mesh( | |
new THREE.CylinderGeometry(25, 25, 40, 40), | |
hatMaterial | |
); | |
hat.position.set(0, 50, 0); | |
const hat_line = new THREE.Mesh( | |
new THREE.CylinderGeometry(26, 25, 20, 30), | |
new THREE.MeshLambertMaterial({ color: 0xe60033 }) | |
); | |
hat_line.position.set(0, 35, 0); | |
const hat_collar = new THREE.Mesh( | |
new THREE.CylinderGeometry(40, 40, 5, 30), | |
hatMaterial | |
); | |
hat_collar.position.set(0, 32, 0); | |
const head = new THREE.Mesh( | |
new THREE.SphereGeometry(40, 40, 20), | |
headMaterial | |
); | |
head.position.set(0, 0, 0); | |
const right_eye = new THREE.Mesh( | |
new THREE.SphereGeometry(5, 25, 40), | |
eyeMaterial | |
); | |
right_eye.position.set(15, 18, 30); | |
const left_eye = new THREE.Mesh( | |
new THREE.SphereGeometry(5, 10, 40), | |
eyeMaterial | |
); | |
left_eye.position.set(-16, 18, 33); | |
const nose = new THREE.Mesh( | |
new THREE.SphereGeometry(5, 30, 20), | |
new THREE.MeshLambertMaterial({ color: 0xed9121 }) | |
); | |
nose.position.set(3, 10, 35); | |
const body = new THREE.Mesh( | |
new THREE.SphereGeometry(50, 50, 50), | |
headMaterial | |
); | |
body.position.set(0, -60, 0); | |
const button_first = new THREE.Mesh( | |
buttonGeometry, | |
buttonMaterial | |
); | |
button_first.position.set(0, -30, 37); | |
const button_second = new THREE.Mesh( | |
buttonGeometry, | |
buttonMaterial | |
); | |
button_second.position.set(0, -40, 43); | |
//ここまで追加 | |
const snowman = new THREE.Group(); | |
snowman.add(hat,hat_line,hat_collar,head,right_eye,left_eye,nose,body,button_first,button_second); //この行を修正 | |
scene.add(snowman); | |
const light = new THREE.DirectionalLight(0xffffff, 0.9); | |
light.position.set(0, 50, 30); | |
scene.add(light); | |
const ambient = new THREE.AmbientLight(0xf8f8ff, 0.9); | |
scene.add(ambient); | |
const camera = new THREE.PerspectiveCamera(70, width / height, 1, 1000); | |
camera.position.set(60, 50, 140); | |
camera.lookAt(scene.position); | |
const renderer = new THREE.WebGLRenderer({ | |
canvas: document.querySelector('#myCanvas'), | |
antialias: true | |
}); | |
renderer.setSize(width, height); | |
renderer.setClearColor(0xe6e6fa); | |
renderer.setPixelRatio(window.devicePixelRatio); | |
function render() { | |
requestAnimationFrame(render); | |
renderer.render(scene, camera); | |
snowman.rotation.y += 0.01; | |
} | |
render(); | |
} |
2つ説明したいことがあります。
1つ目は27行目・33行目の「.CylinderGeometry( )」ですが引数の内容は↓になります。
.CylinderGeometry( 上面半径 , 下面半径 , 高さ , 円周分割数)
「.CylinderGeometry( )」で円柱を作ることができて円周分割数は上面と下面の円の滑らかさを表して値が大きくなるほど円に近づきます。
2つ目は39行目・45行目・51行目・57行目63行目の「.SphereGeometry( )」ですが引数の内容は↓になります。
.SphereGeometry(半径,緯度分割数,経度分割数)
「.SphereGeometry( )」で球体を作ることができて経度分割数は横方向の頂点数で緯度分割数は縦方向の頂点数を表していて値が大きくなるほど球に近づきます。
最後にマウスを使って雪だるまの向きを変えることができるようにします。
マウスを使って雪だるまの向きを変えることができるようにする
HTMLの6行目の内容をJavaScriptで使うためにコードを↓にします。
window.addEventListener('DOMContentLoaded', init); | |
function init() { | |
const width = 560; | |
const height = 300; | |
const scene = new THREE.Scene(); | |
const buttonGeometry = new THREE.BoxGeometry(5, 5, 5) | |
const hatMaterial = new THREE.MeshLambertMaterial({ color: 0x333333 }) | |
const headMaterial = new THREE.MeshLambertMaterial({ color: 0xffffff }) | |
const buttonMaterial = new THREE.MeshLambertMaterial({ color: 0x228b22 }) | |
const eyeMaterial = new THREE.MeshToonMaterial({ color: 0x000000 }); | |
const hat = new THREE.Mesh( | |
new THREE.CylinderGeometry(25, 25, 40, 40), | |
hatMaterial | |
); | |
hat.position.set(0, 50, 0); | |
const hat_line = new THREE.Mesh( | |
new THREE.CylinderGeometry(26, 25, 20, 30), | |
new THREE.MeshLambertMaterial({ color: 0xe60033 }) | |
); | |
hat_line.position.set(0, 35, 0); | |
const hat_collar = new THREE.Mesh( | |
new THREE.CylinderGeometry(40, 40, 5, 30), | |
hatMaterial | |
); | |
hat_collar.position.set(0, 32, 0); | |
const head = new THREE.Mesh( | |
new THREE.SphereGeometry(40, 40, 20), | |
headMaterial | |
); | |
head.position.set(0, 0, 0); | |
const right_eye = new THREE.Mesh( | |
new THREE.SphereGeometry(5, 25, 40), | |
eyeMaterial | |
); | |
right_eye.position.set(15, 18, 30); | |
const left_eye = new THREE.Mesh( | |
new THREE.SphereGeometry(5, 10, 40), | |
eyeMaterial | |
); | |
left_eye.position.set(-16, 18, 33); | |
const nose = new THREE.Mesh( | |
new THREE.SphereGeometry(5, 30, 20), | |
new THREE.MeshLambertMaterial({ color: 0xed9121 }) | |
); | |
nose.position.set(3, 10, 35); | |
const body = new THREE.Mesh( | |
new THREE.SphereGeometry(50, 50, 50), | |
headMaterial | |
); | |
body.position.set(0, -60, 0); | |
const button_first = new THREE.Mesh( | |
buttonGeometry, | |
buttonMaterial | |
); | |
button_first.position.set(0, -30, 37); | |
const button_second = new THREE.Mesh( | |
buttonGeometry, | |
buttonMaterial | |
); | |
button_second.position.set(0, -40, 43); | |
const snowman = new THREE.Group(); | |
snowman.add(hat,hat_line,hat_collar,head,right_eye,left_eye,nose,body,button_first,button_second); //この行を修正 | |
scene.add(snowman); | |
const light = new THREE.DirectionalLight(0xffffff, 0.9); | |
light.position.set(0, 50, 30); | |
scene.add(light); | |
const ambient = new THREE.AmbientLight(0xf8f8ff, 0.9); | |
scene.add(ambient); | |
const camera = new THREE.PerspectiveCamera(70, width / height, 1, 1000); | |
camera.position.set(60, 50, 140); | |
camera.lookAt(scene.position); | |
const renderer = new THREE.WebGLRenderer({ | |
canvas: document.querySelector('#myCanvas'), | |
antialias: true | |
}); | |
renderer.setSize(width, height); | |
renderer.setClearColor(0xe6e6fa); | |
renderer.setPixelRatio(window.devicePixelRatio); | |
function render() { | |
requestAnimationFrame(render); | |
renderer.render(scene, camera); | |
snowman.rotation.y += 0.01; | |
} | |
render(); | |
var controls = new THREE.OrbitControls(camera, renderer.domElement); //この行を追加 | |
} |
これで完成です。