WebGL Advent Calendar 2014の12日目が空きっぱなしだったので書いてみました。
JavaScriptを1行も書かずに出来るWebGLの新しい面白さを紹介します。
#「JavaScriptそのものが苦手」な人のためのWebGL入門記事です
恐ろしいほどに長い初期化、意味不明なGLSL、名前も聞きたくない行列&ベクトル・・・
WebGLはブラウザで3DCGが出来るという魅力で多くの人を惹きつけましたが同時に多くの挫折を生み出してきました。
難しすぎるのです。
「three.js使えば簡単じゃん。」
確かにWebGLを自力で書くよりは50倍くらい楽です。
しかしそれは重要な事実が考慮されていません。
それは「そもそもJavaScript自体が難しい」ということ。
遅い・重いと批判されるjQueryがそれでも圧倒的な人気を誇っているのはJavaScript自体の難しさを解決した唯一のライブラリだからでしょう。
私も未だにjQueryがないとまともにウェブアプリを作れる自信がありません。
今回はそんな「JavaScriptが難しい」と感じている人のためのWebGL入門記事です。
ウェブで3Dが出来るという素晴らしい体験を一部の優秀なプログラマだけが独占していることは人類にとって大きな損失だと思っています。
WebGLが与えてくれる楽しさや感動を今日初めてプログラミングに出会った人たちにも伝えたい思いで書き綴ります。
##開発環境を準備しよう
WebGLをシンプルに書けるライブラリ「jThree」を使って3DCGを描画します。
http://jthree.jp/にアクセスして以下のようにクリックしてZIPをダウンロードしてください。
ZIPを解凍すると以下のようなディレクトリ構造になっているはずです。
ここにHTML,CSS,JS,GOMLファイルを設置します。
(GOMLとはjThreeが定義している3Dオブジェクトのマークアップ言語です)
スクリプト0行でWebGL-開発環境の準備にアクセスして「DL」をクリックするとZIPをダウンロードできます。
ZIPを解凍すると以下4つのファイルが揃っているはずです。
(アイコンはPCの環境によって違います)
この4つのファイルを最初にZIP解凍したディレクトリのトップに移します。
以下のような構造になっていれば正解です。
あとはこのディレクトリを丸ごとウェブサーバーかローカルサーバーにアップロードしましょう。
セキュリティの都合上、WebGLの開発をするにはブラウザからhttpスキームでアクセスしないといけないのです。
Macだと比較的簡単にローカルサーバーが立ち上がるらしいですがWindowsだとかなり面倒です。
ローカルサーバーについて解説はしないのでサーバーの用意が出来ない場合は以下の手順で上記と同じ環境をブラウザ上で再現できます。
スクリプト0行でWebGL-開発環境の準備にもう一度アクセスして「保存」をクリックし「OK」を選択すると編集が可能になります。
ここまで準備が出来たらブラウザでHTMLファイルにアクセスしてみましょう。
(WebGL Editorの場合は「保存」ボタンをもう一度押してください。)
以下の右半分のように真っ白な画面にFPSのメーターが表示されたら準備完了です。
##GOMLをなんとなく理解しよう
GOMLは3Dオブジェクトを描画するマークアップ言語です。
WebGL上に描きたい世界をタグだけで記述することが出来ます。
「index.goml」を開いてみましょう。以下のような内容が書かれています。
(WebGL Editorの場合は画面左上部にGOMLが表示されているはずです。)
<goml>
<head>
<rdr frame="body" camera="camera:first" param="antialias: true; clearColor: #fff;" />
</head>
<body>
<scene>
<camera style="cameraFar: 10000; position: 2 18 30; lookAtY: 10;">
<light type="Dir" style="position: 1 3 5;" />
</camera>
<light type="Amb" style="lightColor: #555;" />
</scene>
</body>
</goml>
gomlタグが全てのルートでheadタグとbodyタグがあるのはHTMLに似ています。
6行目のsceneタグがWebGLの3次元空間を生成します。
cameraタグ1つとlightタグ2つが空間内に設置されていることがわかりますね。
3行目のrdrタグはHTMLのcanvasタグを管理していますが今回は触れません。
これでなんとなくGOMLを理解できたのではないでしょうか。
##MMDモデルの「初音ミク」を描画しよう
いきなりメインです。とはいってもたった1行記述するだけで簡単。
以下のmmdタグをsceneタグ内に追加してみましょう。
<mmd model="model/miku/index.pmx" />
<goml>
<head>
<rdr frame="body" camera="camera:first" param="antialias: true; clearColor: #fff;" />
</head>
<body>
<scene>
<mmd model="model/miku/index.pmx" />
<camera style="cameraFar: 10000; position: 2 18 30; lookAtY: 10;">
<light type="Dir" style="position: 1 3 5;" />
</camera>
<light type="Amb" style="lightColor: #555;" />
</scene>
</body>
</goml>
保存すると直立不動のミクさんが現れます。
マウスで回転やズームなどアングルを変えて3Dであることを体感してください。
スクリプト0行でWebGL-初音ミクの描画でも確認できます。
追加したmmdタグを読むとmodel属性のURLを変えれば好きなモデルを描画できることが想像出来ますね。
<mmd model="model/miku/index.pmx" />
を
<mmd model="model/neru/index.pmx" />
に変えてみましょう。
モデルが変わりました。
##MMDのステージを描画しよう
背景が真っ白だと寂しいので街のモデルを描画します。
これもたった1行追加するだけなので簡単です。
以下のobjタグをsceneタグ内に追加してみましょう。
<obj model="stage/gekido/index.x" style="scale: 10; positionY: -46.5; rotateY: 1.57;"/>
<goml>
<head>
<rdr frame="body" camera="camera:first" param="antialias: true; clearColor: #fff;" />
</head>
<body>
<scene>
<mmd model="model/miku/index.pmx" />
<obj model="stage/gekido/index.x" style="scale: 10; positionY: -46.5; rotateY: 1.57;"></obj>
<camera style="cameraFar: 10000; position: 2 18 30; lookAtY: 10;">
<light type="Dir" style="position: 1 3 5;" />
</camera>
<light type="Amb" style="lightColor: #555;" />
</scene>
</body>
</goml>
保存すると街が現れます。(サンプルではmmdタグのURLをミクさんに戻しました)
追加したobjタグのmodel属性にはステージファイルのURLが記述されています。
style属性で細かい描画方法をしています。数値をいじってみて変化を体感してください。
・scale:拡大率
・positionY:上下方向(Y軸方向)の描画位置
・rotateY:Y軸の回転数(単位はラジアンなので1.57はだいたい90°です)
<obj model="stage/gekido/index.x" style="scale: 10; positionY: -46.5; rotateY: 1.57;"/>
スクリプト0行でWebGL-ステージの描画でも確認できます。
##球体の上にミクさんを立たせる
先ほどのobjタグを削除してミクだけが表示されるようにしておきます。
削除したobjタグの代わりに以下のmeshタグを追加しましょう。
<mesh geo="#geo1" mtl="#mtl1"></mesh>
meshタグは箱や球体など一般的な物体を描画するタグです。
ただしこのタグ1つだけでは何も現れません。
WebGLで物体を描画するためには「形状」と「材質」の情報が必要です。
物体=形状+材質
jThreeでは形状を保持する「geoタグ」と材質を保持する「mtlタグ」をそれぞれmeshタグから参照することで描画できます。
ここで先ほど追加したmeshタグをもう一度見てみましょう。
<mesh geo="#geo1" mtl="#mtl1"></mesh>
geo属性とmtl属性にそれぞれCSSセレクタが指定されています。
これでgeoタグとmtlタグを参照していることがなんとなくイメージできますね。
それでは肝心のgeoタグとmtlタグを追加しましょう。
今度はsceneタグではなくheadタグ内に以下を記述してください。
<geo id="geo1" type="Sphere" param="10 64 64" /> <mtl id="mtl1" type="MeshPhong" param="color: #0ff; specular: #fff;" />
geoタグを読むとtype属性に"Sphere"が指定されているので球体であることがわかります。
param属性の10で半径を指定し、2つの64は多いほど完全な球体に近づきます。
mtlタグのtype属性"MeshPhong"はつややかな材質であることを示しています。
param属性で基本部分と反射部分の色を指定しました。
<goml>
<head>
<geo id="geo1" type="Sphere" param="10 64 64" />
<mtl id="mtl1" type="MeshPhong" param="color: #0ff; specular: #fff;" />
<rdr frame="body" camera="camera:first" param="antialias: true; clearColor: #fff;" />
</head>
<body>
<scene>
<mmd model="model/miku/index.pmx" />
<mesh geo="#geo1" mtl="#mtl1"></mesh>
<camera style="cameraFar: 10000; position: 2 18 30; lookAtY: 10;">
<light type="Dir" style="position: 1 3 5;" />
</camera>
<light type="Amb" style="lightColor: #555;" />
</scene>
</body>
</goml>
保存して以下のように描画されたら正解です。
めりこんだままだとミクさんがかわいそうなので球体の位置を下げましょう。
meshタグのstyle属性を追加します。上下の位置を設定したいのでpositionYプロパティを使い、球体の半径10だけ下げます。
<mesh geo="#geo1" mtl="#mtl1" style="positionY: -10;"></mesh>
ミクさんにめり込まなくなりました。
スクリプト0行でWebGL-球体の描画で確認できます。
##球体に画像を貼って地球にする
以下のような画像を先ほどの球体の表面に貼り付けてみます。
以下のようなtxrタグをheadタグ内に追加しましょう。
(画像や動画など物体の表面に表示するデータのことをテクスチャと呼びます。)
<txr id="txr1" src="img/earth.jpg" />
テクスチャは材質情報の一つなのでmtlタグから参照させます。
headタグ内に記述済みのmtlタグのparam属性でmapプロパティを使いtxrタグを参照するCSSセレクタを指定します、
<mtl id="mtl1" type="MeshPhong" param="map: #txr1; color: #0ff; specular: #fff;" />
<goml>
<head>
<txr id="txr1" src="img/earth.jpg" />
<geo id="geo1" type="Sphere" param="10 64 64" />
<mtl id="mtl1" type="MeshPhong" param="map: #txr1; color: #0ff; specular: #fff;" />
<rdr frame="body" camera="camera:first" param="antialias: true; clearColor: #fff;" />
</head>
<body>
<scene>
<mmd model="model/miku/index.pmx" />
<mesh geo="#geo1" mtl="#mtl1" style="positionY: -10;"></mesh>
<camera style="cameraFar: 10000; position: 2 18 30; lookAtY: 10;">
<light type="Dir" style="position: 1 3 5;" />
</camera>
<light type="Amb" style="lightColor: #555;" />
</scene>
</body>
</goml>
これで地球っぽくなりました。
ところが妙に青いですね。なぜでしょう。
mtlタグのparam属性で「color: #0ff;」が指定されているのが原因です。
削除してみましょう。
<mtl id="mtl1" type="MeshPhong" param="map: #txr1; specular: #fff;" />
より地球らしくなりました。
おまけとしてリアリティを演出するために反射光を無くしそのうえ凸凹感を出してみましょう。
反射光を無くすにはmtlタグのparam属性から「specular: #fff;」を削除します。
凸凹感を出すには同じくparam属性に「bumpScale: 0.3;」を追加します。
数字が大きいほど凸凹がハッキリします。
<mtl id="mtl1" type="MeshPhong" param="map: #txr1; bumpScale: 0.3;" />
かなりリアルな地球になりましたね。
スクリプト0行でWebGL-画像の貼り付けで確認できます。
##スプライトで太陽を表現しよう
宇宙っぽさを演出するために背景を黒くします。
rdrタグのparam属性でclearColorプロパティを「#000」に変更してください。
<rdr frame="body" camera="camera:first" param="antialias: true; clearColor: #000;" />
かなり宇宙っぽくなってきたところで太陽を描画してみましょう。
今回はスプライトという技術を使います。
スプライトとは点オブジェクトのことです。
常に正面を向き続けるので太陽のようにどこから見ても同じ物体を効率よく描画できます。
以下の画像をスプライトに貼り付けます。
まずはtxrタグをheadタグ内に用意しましょう。
<txr id="txr2" src="img/sun.jpg" />
次にスプライト用のmtlタグを使います。type属性を「Sprite」にしてparam属性で上のtxrタグを参照させましょう。
<mtl id="mtl2" type="Sprite" param="map: #txr2;" />
最後にスプライト本体。以下のspriteタグをsceneタグ内に記述します。
「点」という形状情報を持っているのでgeo属性は不要です。
<sprite mtl="#mtl2" />
<goml>
<head>
<txr id="txr1" src="img/earth.jpg" />
<txr id="txr2" src="img/sun.jpg" />
<geo id="geo1" type="Sphere" param="10 64 64" />
<mtl id="mtl1" type="MeshPhong" param="map: #txr1; bumpScale: 0.3;" />
<mtl id="mtl2" type="Sprite" param="map: #txr2;" />
<rdr frame="body" camera="camera:first" param="antialias: true; clearColor: #000;" />
</head>
<body>
<scene>
<mmd model="model/miku/index.pmx" />
<mesh geo="#geo1" mtl="#mtl1" style="positionY: -10;"></mesh>
<sprite mtl="#mtl2" />
<camera style="cameraFar: 10000; position: 2 18 30; lookAtY: 10;">
<light type="Dir" style="position: 1 3 5;" />
</camera>
<light type="Amb" style="lightColor: #555;" />
</scene>
</body>
</goml>
これでミクさんの足もとに小さな太陽が半分だけ現れます。
太陽の大きさを100倍にして500ほど後方(Z軸のマイナス方向)に下げましょう。
spriteタグにstyle属性を追加してscaleプロパティとposotionZプロパティで設定します。
<sprite mtl="#mtl2" style="scale: 100; positionZ: -500;" />
ミクの頭のあたりに太陽が描画されました。
マウスでアングルを変えても太陽が常に正面を向き続けます。
スクリプト0行でWebGL-スプライトでも確認できます。
##テキストを表示しよう
最後は3次元空間内にスプライトで名前を表示してみましょう。
importタグを使うとHTMLタグをGOML内に埋め込むことが出来ます。
さらにtxrタグで埋め込んだHTMLをテクスチャ化し、mtlタグから参照します。
<import>
<style>
div {
position: absolute;
font-weight: bold;
color: #fff;
}
</style>
<div id="name">初音ミク</div>
</import>
<txr id="txr3" html="#name" />
<mtl id="mtl3" type="Sprite" param="map: #txr3;" />
txrタグのhtml属性でimportタグ内のテクスチャにしたいHTML要素を参照しています。
参照される要素(この場合はdivタグ)に「position: absolute;」を指定すると正しく描画されやすくなります。
あとはスプライトを追加するだけです。
以下のタグをsceneタグ内に記述してください。
<sprite mtl="#mtl3" style="scaleX: 3; positionY: 22;" />
style属性のscaleXで横方向(X軸方向)に3倍し、positionYで高さを22に設定しました。
<goml>
<head>
<txr id="txr1" src="img/earth.jpg" />
<txr id="txr2" src="img/sun.jpg" />
<geo id="geo1" type="Sphere" param="10 64 64" />
<mtl id="mtl1" type="MeshPhong" param="map: #txr1; bumpScale: 0.3;" />
<mtl id="mtl2" type="Sprite" param="map: #txr2;" />
<import>
<style>
div {
position: absolute;
font-weight: bold;
color: #fff;
}
</style>
<div id="name">初音ミク</div>
</import>
<txr id="txr3" html="#name" />
<mtl id="mtl3" type="Sprite" param="map: #txr3;" />
<rdr frame="body" camera="camera:first" param="antialias: true; clearColor: #000;" />
</head>
<body>
<scene>
<mmd model="model/miku/index.pmx" />
<mesh geo="#geo1" mtl="#mtl1" style="positionY: -10;"></mesh>
<sprite mtl="#mtl2" style="scale: 100; positionZ: -500;" />
<sprite mtl="#mtl3" style="scaleX: 3; positionY: 22;" />
<camera style="cameraFar: 10000; position: 2 18 30; lookAtY: 10;">
<light type="Dir" style="position: 1 3 5;" />
</camera>
<light type="Amb" style="lightColor: #555;" />
</scene>
</body>
</goml>
名前が表示されるようになりました。スプライトなのでアングルを変えても正面を向き続けます。
スクリプト0行でWebGL-HTMLでテキストで確認できます。
##ミクさんに踊ってもらおう
無理やりJavaScriptを埋め込むのであまりいい方法ではありませんがせっかくなので躍らせてみましょう。
mmdタグを以下のように書き換えます。
<mmd model="model/miku/index.pmx" motion="motion/melt.vmd" onLoad="parent.jThree.MMD.play( true );" />
motion属性でMMDのモーションデータを指定しました。
onLoad属性に記述したスクリプトでモデルの読み込み完了時にモーションをループ再生します。
ミクさんがメルトを踊ってくれました。
スクリプト0行でWebGL-おまけで確認できます。
##まとめ
オブジェクトの生成が全てタグの記述だけで出来ることを紹介しました。
これならWebGLでコンテンツを作るのがもっと楽しくなることでしょう。
ここから先は有志で作っているサイトjThree備忘録を読んでください。
アニメーションやマウスイベント取得など動的な処理はjQueryの知識が必要です。
Webデザイナー、jQuery初心者でも簡単に出来るWebGLライブラリ「jThree」を使ってみよう!が参考になるでしょう。
立体音響やサイト一部への埋め込みなどの応用はtakemikami's noteで解説されてます。
jThree開発者が講師をする勉強会はもう全て終了しましたが最後の公式イベントとして12/23に渋谷でハッカソンを開催します。
jThreeの使い方を直接学びたい方のご参加もお待ちしてます。
【3DCG・立体音響の面白さで勝負】日本初Web3Dハッカソンで年越ししよう!
##データ提供(敬称略)