-
-
WebGL+HTML5で3DCGを勉強してみた! Part1
どうも。営業部の伊藤です。
前回のブログで書いたように、今更感は否めないですが…最近WebGLの勉強をしています。
まだ超々初心者レベルまでしか勉強できていないですが、これはなかなか頭の体操になります。
今まであまり経験した事のない思考が必要という事もあり、抜け毛の本数が増えそうではありますが…初心者レベルになれるまでは頑張ろうと思います。という事で、復習も兼ねてWebGLを利用した3Dグラフィックの描画方法について書こうと思います。
※3DCG、WebGLに着目しているため、実装で利用するHTML、JavaScript、数学的知識の基礎については省かせていただきます。3DCGのサンプルは以下をご覧ください。

※ピカピカしているのは描画遅延ではないです。チカチカしたらすいません。。。【補足情報】
利用しているオープンソースは以下2種類となります。
○Google webgl-utils.js
setIntervalやsetTimeoutだと色々と問題が多い(複数オブジェクトの描画に向いていない、処理の効率化ができない(GPUに優しくない))ので、上記再帰処理はwebgl-utilsを利用しています。○glMatrix.js
3DCGを表現するために必要となる行列計算用ライブラリとして利用しています。
後述しますが、STEP6の座標変換行列を生成&通知のところで利用します。勉強に利用しているサイトは以下になります。
○wgld.org
初心者向けに基礎から細かくレクチャーしてくれており、非常に分かりやすいです。オススメ!!
それではサンプルをもとに実装内容を書いていきます。
3DCG描画までのSTEPは以下のようになります。STEP1 canvasエレメントを取得
STEP2 canvasからWebGLコンテキストを取得
STEP3 シェーダを生成&コンパイル
STEP4 モデルデータを用意
STEP5 モデルデータからVBO(VertexBufferObject)を生成&バインド
STEP6 座標変換行列を生成&通知
STEP7 描画命令
STEP8 STEP6~7の再帰処理(canvasをレンダリング)それでは早速STEP1から処理の内容から確認していきます。
■STEP1(canvasエレメントを取得)
WebGLを利用した3DレンダリングはHTML5で策定されたcanvasタグ上で動きますので、まずはHTML内のcanvasエレメントを取得します。
今回の場合、以下部分でエレメントを取得しています。var canvas = document.getElementById(“cube”);■STEP2(canvasからWebGLコンテキストを取得)
次に、描画処理用オブジェクトとして、STEP1で取得したcanvasエレメントからWebGLコンテキストを取得します。
このコンテキストで各種描画処理や属性の設定をするため、ここでコンテキストが取得できなれけばWebGLを利用できないブラウザという事になります。
今回の場合、以下部分でコンテキストを取得しています。gl = c.getContext(“webgl”) || c.getContext(“experimental-webgl”);
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.enable(gl.DEPTH_TEST);canvasに”webgl”という名前のコンテキストを要求しますが、仕様が策定中の時は失敗してしまうため”experimental-webgl”という名前のコンテキストも要求します。(失敗時用)
その後、コンテキスト内部を初期化(clearColor)し、黒色に塗りつぶしています。
3行目のenable(gl.DEPTH_TEST)はコンテキストに深度(DEPTH)を計算(TEST)しておけよ!という命令です。
近くにあるものが遠くにあるものを隠すためにも描画の前に深度を把握しておく必要があります。サクサク進みますね。
以外と簡単なのでは?と僕もここまでは今までの知識で問題なく吸収できたのですが、STEP3から急に難しくなり髪の毛が数本抜けたのを覚えています。。。■STEP3(シェーダを生成&コンパイル)
まず3D関係の知識が無い人はここで壁にぶるつかるでしょう。
「シェーダ」ってなに?
ウィキペディア先生には以下のように記載されています。主にライティング(光源計算)・シェーディング(陰影処理)とレンダリング(ピクセル化)を実行するためにグラフィックリソースに対して使用するソフトウェア命令の組み合わせである。
「shade」とは「次第に変化させる」「陰影・グラデーションを付ける」という意味で、「shader」は頂点色やピクセル色などを次々に変化させるもの(より具体的に、狭義の意味で言えば関数)を意味する。
※wikipedia内の文章を引用難しい………
超々初心者なりに要約します(間違えていたらすいません)。
ソース内のコメントにも記載しておりますが、シェーダには大きく以下2種類のシェーダ存在します。1. バーテックスシェーダ(頂点シェーダ)
2. フラグメントシェーダ(ピクセルシェーダ)【バーテックスシェーダ(頂点シェーダ)】
3次元描画はポリゴンで表示する事が多く、ポリゴンは一般的に三角形の面をつなげたものが使われます(四角形を利用する事もあります)。
三角形が利用されるポイントは、三角形は頂点の位置さえ決めれば面の位置やサイズが確定するという点です。
四角形、五角形、、、それ以上の多角形も頂点だけでは面の位置やサイズを決める事はできません。
この頂点の位置を計算するのがバーテックスシェーダ(頂点シェーダ)です。
※バーテックスシェーダは頂点の位置情報だけ管理する訳ではなく、頂点に関する”全ての情報(頂点の法線、テクスチャ座標、頂点色など)”を管理します。【フラグメントシェーダ(ピクセルシェーダ)】
バーテックスシェーダ(頂点シェーダ)で頂点の位置や形状を決め、その後ラスタライズ(着色前準備)まで終えると、次にオブジェクトへの「着色」が必要になります。
この着色内容を計算、決定する役割を担うのがフラグメントシェーダ(ピクセルシェーダー)です。
色の計算だけであれば簡単に思えますが、これがそうでもないんです。
3次元を表現するため、陰影、深度、光源、重複(オブジェクトやテクスチャの重なり)、乗算(透過時の後ろのテクスチャ色)などを考慮した着色計算が必要です。
よって、上記のような色を計算するため、バーテックスシェーダ(頂点シェーダ)の情報が不可欠になります。
このシェーダ連携についてはSTEP6で書かせていただきます。シェーダの生成&コンパイルは以下部分で実施しています。
var shader;
var shaderElement = document.getElementById(id);
if (!shaderElement) {
return null;
}switch(shaderElement.type){
case “x-shader/x-vertex”:
shader = gl.createShader(gl.VERTEX_SHADER);
break;
case “x-shader/x-fragment”:
shader = gl.createShader(gl.FRAGMENT_SHADER);
break;
default:
return null;
}//子ノード取得
var source = “”;
var childNodes = shaderElement.firstChild;
while (childNodes) {
if (childNodes.nodeType == 3) {
source += childNodes.textContent;
}
childNodes = childNodes.nextSibling;
}
gl.shaderSource(shader, source); // ソース割り当て
gl.compileShader(shader); // コンパイル見慣れないエレメントタイプがありますね。
「x-shader/x-vertex」
「x-shader/x-fragment」JavaScriptファイルではなく、HTMLファイルのheader部分も見てみてください。
この2つのキーワードが上記で説明したバーテックスシェーダ(頂点シェーダ)とフラグメントシェーダ(ピクセルシェーダ)です。<script id=”vertex” type=”x-shader/x-vertex”>
attribute vec3 vertexPosition;
attribute vec2 vertexTexture;
uniform mat4 vertexMatrix1;
uniform mat4 vertexMatrix2;
varying vec2 textureCoord;void main(void) {
gl_Position = vertexMatrix1 * vertexMatrix2 * vec4(vertexPosition, 1.0);
textureCoord = vertexTexture;
}
</script>
<script id=”fragment” type=”x-shader/x-fragment”>
precision mediump float;
uniform sampler2D fragmentTexture;
varying vec2 textureCoord;void main(void) {
gl_FragColor = texture2D(fragmentTexture, vec2(textureCoord));
}
</script>中途半端にはなりますが…
ここから説明が一気に難しくなる&細かくなり始めるので今回のブログはここまでにします。ではでは。
カテゴリー 技術 / デザイン / 制作 -
そうだ、アプリの研究しよう vol.0
-
videoタグで動画検証。
少しHTML5のvideoタグについて検証したので纏めます。
HTML5では、動画再生する為にタグが追加されています。
このタグを使用することで、プラグインをインストールしてもらうことなく、
HTML側から簡単に動画を扱えるようになります。細かい属性などについてはこちらをご確認下さい。
以下のように各端末で読み込まれる動画を上から並べていきます。
<video id="video" width="300" height="200" controls="controls" onclick="this.play()" preload="metadata"> <source src="test.webm" type="video/webm"> <!-- OGG/WebM 陣営向けの動画 --> <source src="test.ogv" type="video/ogg" media="all"> <!-- MPEG-4 陣営向けの動画 --> <source src="test.mp4" type="video/mp4" media="all"> </video>
以下の指定で動画が再生されるはずが、落とし穴が二つ!
まず、GALAXYS で再生が出来ない!
GALAXYSは<video>タグを設置するだけでは再生できないようです。Javascriptで以下を指定し再生する必要があります。
// JavaScript Document $(function(){ var video = document.getElementById('video'); video.addEventListener('click',function(){ video.play(); },false); });その2!
まさかのandroid 4.0 以降はストリーミング再生が不安定。android 2.0.× ~ 2.3.× などのバージョンは端末の動画プレイヤーを立ち上げるので
読み込む時間が多少掛りますがみる事が出来ます。android 4.0 以上はストリーミング再生で重い動画だと
音声だけが再生されたり、固まったり不安定です。(Wi-Fiでもかなりきつかったです。LTEなら快適かも?)1MB以下の短い15秒位の動画でやっと再生出来ました。
再生したいのは約1.5MBの1分程の動画です。検証端末は
・GALAXYS android 2.3.6
・GALAXYS3 android 4.0
・iPhone4S、5
・他android端末
です。まだまだ動画系はvideo タグでOK!というわけにはいかないようです・・・。
もしかしたらjsの制御で改善出来るかもしれませんが調べた限りでは無さそうでした。
android 4.0 も端末の動画プレイヤーで再生出来れば
まだストレスを感じずに再生出来るのではないかと思います。なんとかならないかな― android
カテゴリー 技術 / デザイン / 制作 -
HTML5+WebGLのゲームが主流に!?WebRTCでKinectのようなゲームやソーシャルゲーム内のサムネイルが動画に!?
どうも。営業部の伊藤です。
今日は11/7です。
先週末の日曜日は11/4です。
そうなんです!そうです。
3~4ヶ月前から着々と準備を進めていた、初ハーフマラソンの日でした。本来であればハーフマラソンを走った感想を書く予定でしたが、、、
そうです。そうなんです!
風邪をひいて参加できなかったんです。
めちゃめちゃ楽しみにしていたハーフマラソンの日に体調不良がピークを迎え、自宅で引きこもりだったんです。
よって、ハーフマラソンの感想は書けない事になりました。。。
※因みにブログを投稿した今日も回復していない状況の為、1週間以上長引いています。
皆さんも体調管理には十分にご注意ください。そんなこんなで1日中引きこもりをしている中、9月頃FacebookのCEOであるザッカーバーグが発言した言葉を思い出しました。
ザッカーバーグが発言したように、まだまだスマートフォンアプリケーションの実装方法には課題を多く残しており、どこの企業もトライ&エラーの日々を繰り返しています。
この発言だけ真に受けるとHTML5によるアプリケーションの実装方法はネイティブ実装に劣っていると感じる方が居るかも知れませんが、アプリのフレームのみネイティブで実装し、
その他画面/UIはHTML + CSS + JavaScriptで実装するようなハイブリッドアプリで成功を収めている企業も多数あります。(クックパッドなど)実現したいものにより都度試行錯誤、検討が必要だと思いますが、個人的にはネイティブ実装は一部グラフィカルな描画を必要とするもの以外では利用されなくなり、その他のアプリケーションについてはHTML5やJavascript、Dartなどが利用されるようになると考えています。
何故そう思うのか理由をいくつか並べてみます。1. ネイティブの強みであるデバイス機能の操作についても徐々にブラウザ側で対応が施されているため、じきにブラウザからネイティブに遜色なく利用が可能になる(と思う)。
2. ウェブ向けプログラミング言語の高速化(安定板のSDKの提供が始まっているDartは、V8エンジンよりも高速にレンダリングが可能)によりオーバーヘッドが気にならなくなる。
3. 通信回線速度の成長(LTE、そして4Gへ)により、サーバーサイドテクノロジーをより発揮できる実装手法/構築方法(ウェブ向けの開発)が選ばれるようになる。
4. クロスプラットフォーム対策、工期短縮、価格圧縮、保守コスト軽減のため、テクノロジーが統一され始める!デバイス間の統一だけでなく、サーバーサイドとクライアントサイドのテクノロジーも統一される(して欲しい)。
現状だとNode(JavaScript)、CoffeeScript、Dart、GWT(JAVA)などが選択肢にありますが、個人的にはGoogleの本気度や政治的問題や各々の言語の特性を考えると、1年後くらいにはDartが今以上に使われているのではと予想しております。5. Web技術の表現力の拡張(HTML5/CSS3を利用したパララックス効果やWebGLを利用した3D表現など)
WebGL(OpenGL)についてはAndroid、iPhone(iAd広告のみ何故か対応)ともに一部Android端末以外未対応の状況ですが、近いうちに解禁されると思います。6. WebRTCの標準化(現状はChromeとOperaだけだが、Firefoxも実装を進めており、当初反対していたMicroSoftもサポートを計画している、いずれスマートフォンブラウザも!?)
と色々とWeb贔屓の理由を書いてみましたが、もちろんネイティブが優れている点も多々あります。
デバイス機能の操作や3Dグラフィカルなどは基本的にはバインディングになりますし、画像の描画においてもブラウザアプリケーション経由のためオーバーヘッドもあります。
今後も当分は実現したい事、ユーザーに提供したいもの、重要視するもの、予算、工期など様々な条件のなか模索していくことになるでしょう。タイトルにも挙げましたが、個人的に特に注目しているのが上記理由の5点目に挙げた「WebGL」と6点目に挙げた「WebRTC」です。
WebGLは「ブラウザ上で別途プラグインなしに3CGを表現できる」フレームワーク、WebRTCは「ウェブアプリケーション同士が”直接双方向通信”」できるフレームワークです。○WebGL
WebGLを用いたアプリケーションは今までにも数々登場していますが、まだまだインタラクティブなものは少なく、どちらかというと3次元空間で表現したビジュアルをブラウザでただ単純に見れるといったものが多いです。
デバイスの対応状況、ソフトウェアの対応状況、技術的な難易度(ライブラリを使わずに全てを把握するとなると正直相当難しいと思います)など理由はいくつか挙げられますが、昨今のデバイスの拡張状況やライブラリの普及を考えると、今後はPlayStationやWiiのようなグラフィカルなゲームもブラウザ上で楽しむのがデファクトスタンダードになるのでは?とも感じています。WebGLを利用した参考サイトのURLをいくつか記載します。(ブラウザはChromeでご覧ください)
http://webglsamples.googlecode.com/hg/aquarium/aquarium.html
http://alteredqualia.com/three/examples/webgl_animation_skinning_tf2.htmlPlayStationもそう遠くはないですね。
顔写真だけで高品質な3D顔を生成できるものもあります。
ヨーロッパのVizagoです。※動画すごいですね!
現在このWebGLの「3D表現の仕組み」を個人的にまったく理解できていないため、基礎から勉強をしようと奮闘中です。
結果についてはまたどこかで投稿させていただきます。○WebRTC
直接双方向通信できますので、ブラウザ上で音声やビデオチャットをRTC(リアルタイムコミュニケーション)することが可能になります。
Cometのように擬似的に同期を表現、且つサーバートリガーで双方向通信する訳ではなく、最近のブログで書いたWebSocketのようにWebアプリケーショントリガーでサーバーと双方向通信する訳でもないです。
ウェブアプリケーション同士で直接双方向通信できるというのがポイントです。
Chrome21から標準で使えるようになっており、KinectのようなゲームやブラウザとiOSデバイスの両方でビデオチャットができる世界も登場しています。WebRTCについてはWebGLの基礎を習得してから勉強してみます。
こちらについてもまたどこかで投稿させていただきます。ではでは。
カテゴリー 技術 / デザイン / 制作 -
WebSocketでチャットアプリを作る(with Redis&Node.js) ~続き~
どうも。営業部の伊藤です。
先日のブログの続きです。
Redisの準備が終わりましたので、次はWebSocketを利用するための環境を用意します。WebSocketを利用するため、今回はサーバー側のテクノロジーとしてNode.jsを利用する事になしました。
Jettyなど他にも選択肢はあったのですが、参考文献が多いという理由でnodeにしました。では、nodeをインストールします。
インストールには、nodeを導入するためのシェルスクリプトでインストールを簡略化してくれる「nave」(複数のバージョン管理も可能)やgit上にも様々なオープンソースがありますが、今回は複数バージョンを管理する必要もないため、普通にソースをダウンロードしてコンパイルする事にしました。
バージョンはインストール当時(2012年9月頃)のstableだった0.8.6にしました。# cd /usr/local/src
# wget http://nodejs.org/dist/v0.8.6/node-v0.8.6.tar.gz
# tar xf node-v0.8.6.tar.gz
# cd node-v0.8.6
# ./configure
# make install次にnode向けのパッケージマネージャであるnpm(Node Package Manager)をインストールします。
npmで管理されているライブラリは以下URLをご覧ください。
https://npmjs.org/
nodeコマンドが利用できるかを確認後、以下を実行します。# node -v
v0.8.6
# curl http://npmjs.org/install.sh | sh対話モードを”yes”と答えていくとインストール完了です。
これでnodeが利用できる状態であれば、npmコマンドも利用できるようになります。
早速バージョンだけ確認してみます。# npm -v
1.1.48次にパッケージをダウンロードするのですが、その前にnodeの動きを一度確認しておきます。
王道のHello Worldを表示するため、nodeコンソールを開き以下スクリプトを実行します。# node
> var http = require(“http”);
> http.createServer(function(req, res){
> res.writeHead(200, {“Content-Type”: “text/plain”});
> res.end(“Hello World”);
> }).listen(3000, “127.0.0.1”);ブラウザで上記URL(http://127.0.0.1:3000/)にアクセスすると、画面上に「Hello World」が表示されます。
単純にテキストを表示するだけなのに、ヘッダーステータスコード200?、listen?
普段はTomcatやApache(+CGI)といったコンテナ上でサーブレットやPHPでアプリケーションを構築する事が多いと思いますが、nodeの場合はアプリケーションだけでなく、上記サンプルのようにnode自体がコンテナとなります。
その為、nodeで例外が発生するとサーバ機能ごとプロセスが終了してしまいますので、サーバ機能まで共倒れしないよう例外処理には気を付ける必要があります。
※nodeコンソール上で実行していますが、上記スクリプトをファイルに保存し(例えばtest.js)、nodeコマンドのパラメータとして実行する事も可能です。ではwebsocketを利用するため、幾つかのパッケージをnpmを利用してインストールします。
1. Express
Expressはnode用のWebフレームワークです。
上述したようにnodeの場合はnode自身がコンテナの役割を果たす必要があるため、静的なファイルの配信についてもnode内で対応しなければなりません。この手間をWebアプリケーションフレームワークであるExpressを利用することで容易に解決できます。
以下コマンドでパッケージをグローバルインストールします。
※グローバルインストールにしないとパスが通らないケースがあるため、以下コマンドでインストールすることをお勧めします。$ npm install -g expressインストールディレクトリにあるapp.jsを実行します。
$ node app.jshttp://127.0.0.1:3000/ にアクセスして「Welcome to Express」と表示されていればインストール完了です。
2. Socket.IO
Socket.IOは今回利用するWebSocketをブラウザの互換性を考慮せずに利用できる抽象化ライブラリです。
WebSocketを利用できないブラウザではAjaxのロングポーリングを利用するなど、ブラウザ間の互換性を気にせずに開発が可能になります。
以下コマンドでパッケージをインストールします。$ npm install socket.ioこれで一通りの準備が終わりましたので、次はスクリプトを用意します。
publicディレクトリ内に移動し、chat.htmlファイルを作成します。<head>
<script src=”/socket.io/socket.io.js”></script>
<script src=”/javascripts/chat.js”></script>
</head>
<body>
<input type=”text” id=”comment” placeholder=”入力してください” value=”” size=20 ></input>
<button onclick=javascript:send() >送信</button>
<p id=”list”></p>
</body>次に、javascriptディレクトリに以下ファイル(chat.js)を作成します。
クライアントサイドのJavaScriptです。var postList;
window.onload=function(){
postList = document.getElementById(“list”);
}var listleft = function(){
postList.innerHTML+=”<div align=’left’><img src=’/images/left.gif’>”+arguments[0]+”</div>”;
}var listright = function(){
postList.innerHTML+=”<div align=’right’>”+arguments[0]+”<img src=’/images/right.gif’></div>”;
}var socket = io.connect(“http://127.0.0.1”, {port:3000});
socket.on(“connect”, function() {
listleft(“接続開始”); //接続した当人にだけ表示
socket.emit(“msg send”, “皆さん宜しくお願いします。”);// 自分のコメントを表示
socket.on(“msg mycommnet”, function(commnet){
listleft(commnet);
});// 他人のコメントを表示
socket.on(“msg elsecommnet”, function(commnet){
listright(commnet);
});socket.on(“disconnect”, function(){
listleft(“接続が切れました。”);
});
});function send(){
var comment = document.getElementById(“comment”).value;
socket.emit(“commnet send”, comment);
}自分が投稿した内容は左側に表示し、自分以外の投稿は右側に表示するようにします。
最後に先ほど動作確認したapp.jsの下段にsocketインスタンス作成処理を追記します。var express = require(“express”);
var app = module.exports = express.createServer();app.configure(function(){
app.set(“views”, __dirname + “/views”);
app.set(“view engine”, “ejs”);
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(express.cookieParser());
app.use(express.session({ secret: “your secret here” }));
app.use(app.router);
app.use(express.static(__dirname + “/public”));
});app.configure(“development”, function(){
app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});app.configure(“production”, function(){
app.use(express.errorHandler());
});app.get(“/”, function(req, res){
res.render(“index”, {
title: “Express”
});
});app.listen(3000);
console.log(“Express server listening on port %d in %s mode”, app.address().port, app.settings.env);//socketインスタンスを作成
var io = require(“socket.io”).listen(app);
io.sockets.on(“connection”, function(socket){
socket.on(“commnet send”, function (commnet) {
socket.emit(“msg mycommnet””, commnet); //自分自身に通知
socket.broadcast.emit(“msg elsecommnet”, commnet); //自分以外で同一ソケットとハンドシェイク中の人にキャスト
});
socket.on(“disconnect”, function() {
log(接続が切れました。”);
});
});スクリプトの準備が整いましたので、再度サーバーを起動します。
$ node app.jsその後、publicディレクトリ内に新規作成したchat.htmlにアクセスします。
http://127.0.0.1:3000/chat.htmlまず始めに、アイコンがダサ過ぎる点については無視してください。。。
クライアント側からハンドシェイク要求を送信し、サーバー側からハンドシェイクの応答があれば自身のブラウザだけに「接続開始」というメッセージが表示されます。
その後、既に同一ソケットにアクセス中のユーザーに接続が確立した旨を伝えるため、「皆さん宜しくお願いします。」とうメッセージを発信(emit)します。
上記画像の場合、左側のブラウザが先にアクセスしたブラウザで、右側のブラウザが後にアクセスしたブラウザです。
右側のブラウザで接続後にコメントを送信すると、リアルアイムに左側のブラウザに投稿内容が反映されました。いい感じ♪
例外処理、DBを利用した機能(過去データ取得など)などは未実装の状態ですが、一旦これでチャットWebアプリの基盤はできました。
次はRedisを利用した過去データ取得、Publish/Subscribe型(Pub/Sub型)とnodeを連携した冗長化を進めていきます。
こちらについては進展があればまたブログに投稿させていただきます。それでは。
カテゴリー 技術 / デザイン / 制作
