WebGLで画像を描画する(いろんなエフェクトをかけてみる

前回の続きですが、
gl.drawElementsとgl.drawArraysを使って画像を描画してエフェクトをかけちゃいます。
今回のサンプル画像も東京ドロンパです。

gl.drawElementsで色んなエフェクトをかける

下記サイトを参考にさせていただきました。
GLSLはこちらに記載しあるもので、時間経過と共にパラメータを更新し再描画している感じです。
http://ics-web.jp/lab/archives/5535

※ サンプルのために描画した下記サイトは、Google Chromeで閲覧下さい。
(Web Componentsやら使ってしまったので閲覧できる環境に制限が。。

・うずを巻く
http://experiment.itinao.net/webgl/blog/01_uzu_texture.html

・モザイク状態から徐々に鮮明になっていく
http://experiment.itinao.net/webgl/blog/02_mosaic_texture.html

・クレヨンで描いたようにボヤケている
http://experiment.itinao.net/webgl/blog/03_crayon_texture.html

・画像の色味が反転する
http://experiment.itinao.net/webgl/blog/04_reverse-color_texture.html

・画像の色が白黒になる
http://experiment.itinao.net/webgl/blog/05_sepia_texture.html

gl.drawArraysでエフェクトをつけてみる

下記を参考にさせていただきました。
こちらはスライドショーになってますが、下記のサンプルでは演出部分だけ抽出してみました。
肝は、canvasのcontext.getImageDataで色の情報を取得するとこです。

・画像がバラバラになったり、くっついたりする

以上です。
画像1つで、色んな見せ方ができるので面白い。。

WebGLで画像を描画する

表題の通り、WebGLで画像を描画します。

描画するにはいくつか方法があると思いますが、
今回は
・gl.drawElementsを使った手法
・gl.drawArraysを使った手法
の2通りを試しました。

描画した結果はこちらです。
なるべくシンプルに書いています。
http://experiment.itinao.net/webgl/texture_draw_sample.html
(サンプル画像は、東京ドロンパです!

ハマりどころになるかと思うので先に書いておきますが、
画像のサイズ(縦と横)は2の乗数にしてください。
そうしないとエラーになって描画できません。
canvasでdrawImage(2の乗数で)して、canvas.toDataURLでリサイズしたデータを取るなどしても大丈夫です。

理由は、古いGPUが2の乗数のテクスチャしかサポートしていないから、などがあるみたいです。

gl.drawElementsを使った手法

おそらく一般的なもの。
コードを一部抜粋すると、このような感じ。
全文を見たい場合は、上記のサンプルコードを見てください!
var tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, textureImg);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.bindTexture(gl.TEXTURE_2D, null);
textureImgは、HTMLのIMG要素です。
ここをVIDEO要素に変えると動画の一コマを描画することができます。

画像の描画に関する詳細は、こちらが参考になります。

動画の描画に関する詳細は、こちらが参考になります。

gl.drawArraysを使った手法

gl.POINTSを使って1つずつパーティクルを描画しています。

canvasのctx.getImageDataで画像データを取得し、その取得した画像データを使い、
ループの中で画像色・その位置を格納していってる感じです。
for (var i = 0; i < this.numParticles; i++) {
    var p = i;
    var cIndex = p * 3;
    var imageindexX = (rate * i % this.width) >> 0;
    var imageindexY = this.height - rate * (rate * i / this.width >> 0) >> 0;
    var pixIndex = (imageindexX + this.width * imageindexY) >> 0;
    vColor[cIndex] = this.imageArr[pixIndex * 4] / 255;
    vColor[cIndex + 1] = this.imageArr[pixIndex * 4 + 1] / 255;
    vColor[cIndex + 2] = this.imageArr[pixIndex * 4 + 2] / 255;
    var pIndex = p * 3;
    vPosition[pIndex] = ((rate * i % this.width) - this.width / 2) / posScale;
    vPosition[pIndex + 1] = (rate * (rate * i / this.width >> 0) - this.height / 2) / posScale;
    vPosition[pIndex + 2] = 0;
}

終わりに。

この他にも画像を描画する手法はあるかもしれませんが、
描画の方法によってできる演出が違うと思うので色々覚えておくと良いかもしれません!

WebGLの描画メソッド・プリミティブについて。

WebGLって何だっけ?

ここ読んで下さい。

筆者もすごく参考になってますありがとうございます。

初めての方は、読んでって言われても..って感じだと思うので、
概要だけ掴んだ後は下記のサンプルを実際にコードを書きながら試してみるといいと思います。
「ポリゴンのレンダリング」http://wgld.org/d/webgl/w014.html
(WebGLで三角形を描画するだけでも数十行コードを書く必要があります。

今回の記事は、上記のポリゴンレンダリングでも使われている、描画メソッド[gl.drawArrays]などについて記載しています。
そして、その描画メソッドで指定されているプリミティブについて解説していきます。
(プリミティブとは、描画メソッドで使用する描画モードみたいなもの。詳細は下記で。

WebGLの描画メソッド

下記の種類があります。これらを実行することで、canvas上に描画されます。
大雑把に書いているので、「gl.drawArrays」と「gl.drawElements」は下記で補足します。

gl.drawArrays
⇒ 頂点情報を配列で正しい順番に格納し、その内容で描画するやつ。

gl.drawElements
⇒ 頂点情報と、頂点情報の何番目を使って描画するのかの情報を格納し、その内容で描画するやつ。
drawArraysとの違いは、頂点情報の順番を気にしないでいいこと。
頂点情報の何番目を使うかを指定するので、頂点情報が重複する場合はデータを省略できる。

gl.clear
⇒ gl.clearColorで指定した色で描画する。初期化用みたいな感じ。

gl.drawArrays

頂点情報を配列で正しい順番で格納し、その内容で描画するやつ。

使い方
gl.drawArraysメソッドを使用する前に、下記の手順を踏まないといけない。
1. gl.createBuffer()でVBO(頂点バッファ)を作成する
2. VBOをgl.bindBuffer()で、ターゲットのgl.ARRAY_BUFFERにバインドする
3. gl.bufferData()で、バッファに頂点データをセットする
4. gl.enableVertexAttribArray()で、汎用頂点属性配列を有効にする
5. gl.vertexAttribPointer()で、頂点シェーダに記述しているattributeをVBOのデータに接続する

引数について
gl.drawArrays(
    enum [レンダリングしたいプリミティブ], 
    int [頂点データの配列で最初に使用する要素のインデックス], 
    int [頂点の数])

enum [レンダリングしたいプリミティブ]
下記のどれかを指定します。(頂点をそれぞれ、右記で表現します。v0, v1, v2, v3...)

gl.LINES 
⇒ 線です。例えば、v0とv1, v2とv3, v4とv5を線で結んで描画されます。
(この場合は線3つできます。

gl.LINE_STRIP 
⇒ 線です。例えば、v0とv1とv2とv3とv4とv5を線で結んで描画されます。
(この場合は各頂点を結んで一筆書き。

gl.LINE_LOOP 
⇒ 線です。例えば、v0とv1とv2とv3とv4とv5を線で結んで描画されます。
(この場合は各頂点を結んで一筆書きですが、終点v5と始点v0も線で結びます。

gl.TRIANGLES 
⇒ 三角形です。例えば、v0とv1とv2, v3とv4とv5を線で結んで描画されます。
(この場合は頂点6つで三角形2つ描画されます。

gl.TRIANGLE_STRIP 
⇒ 三角形です。例えば、v0とv1とv2, v1とv2とv3, v2とv3とv4を線で結んで描画されます。
(この場合は頂点5つで三角形3つ描画されます。じぐざぐ。

gl.TRIANGLE_FAN 
⇒ 三角形です。例えば、v0とv1とv2, v0とv2とv3, v0とv3とv4を線で結んで描画されます。
(この場合は頂点5つで三角形3つ描画されます。扇型。

gl.POINTS 
⇒ 点です。指定した頂点を点で描画します。点の大きさは[gl_PointSize]で指定できます。

合わせて読みたい資料はこちらです。
とっても参考になります。図付きですし。

※ このように、描画する手段が色々あるので、最適な描画手段を選ぶ必要があります。
正方形を描画するにしても下記パターンがあり、
使用する頂点数が少なければ処理すべきデータ数が減り、メモリからGPUに転送するデータ量も減ります。
・gl.TRIANGLESで頂点を6つ指定するパターン
・gl.TRIANGLE_STRIPやgl.TRIANGLE_FANで頂点を4つ指定するパターンなど

・int [頂点データの配列で最初に使用する要素のインデックス]
頂点データの配列で最初に使用する要素のインデックスを指定する。

・int [頂点の数]
頂点の数を指定する。

gl.drawElements

頂点情報と、頂点情報の何番目を使って描画するのかの情報を格納し、その内容で描画するやつ。
drawArraysとの違いは、頂点情報の順番を気にしないでいいこと。
頂点情報の何番目を使うかを指定するので、頂点情報が重複する場合はデータを省略できる。

使い方
gl.drawElementsメソッドを使用する前に、
gl.drawArraysの手順に加えて、下記の手順を踏まないといけない。
1. gl.createBuffer()でIBO(インデックスバッファ)を作成する
2. IBOをgl.bindBuffer()で、ターゲットのgl.ELEMENT_ARRAY_BUFFERにバインドする
3. gl.bufferData()で、VBOを使用する順序を決定するインデックス配列をセットする

引数について
gl.drawElements(
    enum [レンダリングしたいプリミティブ], 
    int [インデックスバッファのインデックス数], 
    enum [インデックスバッファに格納されている要素の型], 
    int [頂点データの配列で最初に使用する要素のインデックス])

enum [レンダリングしたいプリミティブ]
gl.drawArraysと同様。

int [インデックスバッファのインデックス数]
インデックスバッファに何個のインデックスがあるかを指定する。

enum [インデックスバッファに格納されている要素の型]
gl.UNSIGNED_BYTE
gl.UNSIGNED_SHORT
このどちらかの型を指定する。

int [頂点データの配列で最初に使用する要素のインデックス]
インデックスバッファのインデックスの開始位置を指定する。

まとめ。

描画メソッド・プリミティブよって、描画する手段が異なります。
正方形を5個くらい描画する場合は
gl.drawElementsとgl.TRIANGLE_STRIPを使うと頂点データの数や使用するデータ用量も一番小さくなります。

ただ、データ容量を小さくするのも大事ですが、
描画するものによって、最適なデータの持ち方を選ぶことも大事らしいです。

今後も何かWebGL関連の記事を書きたいです。

以上。


RecordRTCを使ったデスクトップ画面共有アプリを作ってみた。

前回記述した
WebRTC(PeerJS)とPolymerを使ったデスクトップ画面共有アプリを作ってみた。
の派生として、
RecordRTCを使ったデスクトップ画面共有アプリを作ってみました。

こちらからインストールできます。
(とりあえず動くものをアップした状態なので、画像とか使い方が書けてないですが。。)
◆ Desktop Capture Recorder
https://chrome.google.com/webstore/detail/desktop-capture-recorder/boajadhokhjkmfjmnmmkkmgnafnbcfco

このアプリが何ができるかというと、デスクトップで開いているアプリケーションを録画し、録画内容のプレビュー・保存を行うことができます

前回記述したアプリではデスクトップ画面のリアルタイム共有ができますが、セキュリティやネットワークのトラフィックなど気になる箇所がありました。
これで問題解決です。。たぶん。

・使い方をザックリと。
1. 拡張機能のアイコンを押す。
2. デスクトップ画面のどの画面を共有するか選ぶ。すると録画開始。
3. 拡張機能のアイコンを押す。すると録画停止。
4. 拡張機能のアイコンを押す。吹き出しが出てくるので、プレビュー見るか保存するか削除するかを選ぶ。
以上。

作り方

要領は前回と一緒で、getUserMediaで取得したstreamを、RecordRTCに流し込むだけ。
簡単です。
シグナリングサーバーも外部への通信も何も入らないので、更に楽。

・RecordRTC
https://www.webrtc-experiment.com/RecordRTC/

まとめ

簡単です。って書きましたが、ハマったところもありました。
何も考えずにgetUserMediaで取得したstreamを、RecordRTCに流し込むと、録画した動画の縦横比率がめちゃくちゃになりました。

どうしたかというと、
1. stream取得
2. videoタグを作成しstreamを流し込む
3. videoタグの[canplay]のeventでvideoWidth/videoHeightを取得する
4. 「3.」で取得したwidthとheightをRecordRTCのオプションに指定する

ということをしました。

要望があればソースも混じえて解説するかもしないかも..です。

以上!

WebRTC(PeerJS)とPolymerを使ったデスクトップ画面共有アプリを作ってみた。

表題の通りですが、
WebRTC(PeerJS)とPolymer(全然凝ってないけど)を使ったデスクトップ画面共有アプリを作ってみました。

これで何ができるかっていうと、
画面上で起動しているアプリケーションをリアルタイムに画面共有ができます。

図. こんなイメージ。


共有する側(図の左側)はChrome拡張機能をインストールする必要があります。

これは、「Desktop Capture API」を使いたかった為です。
chrome.desktopCapture
⇒ https://developer.chrome.com/extensions/desktopCapture

共有を開始すると共有用URLが生成されるので、
こちらを閲覧する側(図の右側)に教えてGoogle ChromeでURLを開くだけでリアルタイムに操作内容が閲覧できます。

◆ Desktop Capture Share
http://screen-share.itinao.asia/

◆ Chrome拡張機能インストール
https://chrome.google.com/webstore/detail/desktop-capture-share/albmbgococdgmmoajacmcbfcidhbinlh

使った技術

Chrome拡張機能側
Knockout.js
⇒ 状態によって表示するものが切り替わるので、フレームワークを使用しました。
無くても書けましたがソースが汚くなっていくー。。って感じだったので導入。

共有されるURL側
Web Components(Polymer)
⇒ 試しに使ってみました。
peer-videoタグを作ったぐらいです。
(属性でホストとかURLを指定すれば勝手にシグナリングしてvideoタグで再生してくれるやつ。)

共通
WebRTC(PeerJS)
⇒ ほんと楽にシグナリングしてくれるんで便利。
(PeerJSサーバーも立てました。)

gulp
⇒ タスクランナーはgulpを使ってみました。
Polymerを使ったので、gulp-vulcanizeを導入してロードファイル数を下げてみる。とかやりました。

まとめ。

これは、技術的にできるなーって思ってから1〜2日で動作の確認までできました。
見た目整えて、gulp走らせるようにして、、ってやってたら1週間以上かかってのリリースになりました。

あと、
RecordRTCを使って録画機能も付けてみようかなーと考え中です。

録画したWebMファイルを接続相手に送信。。なんてこともできちゃいますね!
なんか色々出来て面白い。

以上!

Shadow DOMについて。

今回はShadow DOMの「content」と「select="セレクタ"」と関連するセレクタらへんの話しにします。

Polymerを勉強したての頃は「?」な部分が多く、

何がよく分からないのかなーと色々と調べたところ、
Web Components(特にShadow DOM)の理解が足りなかったんだなってことが分かりました。
(きっといきなりPolymerを覚えると、jQueryは触れるけどJavaScriptよくわかんないみたいな感じ?
なので、今回はShadow DOMがテーマです。

今回のサンプルコードは下記のAPI使っています。
HTML Templates
Shadow DOM

筆者の実行環境は【Google Chromeのバージョン 37.0.2062.124】です。
(Shadow DOM は Chrome 35 以降で利用可能らしいです)

Shadow DOMって?

以前の記事「Web ComponentsとPolymerをさわってみる。」
にも記述しましたが、
【DOM要素のレンダリング結果を、その要素が持つDOMのサブツリーとは独立に与える仕組み。】です。

ページの他の部分から切り離され、CSSの記述を間違って同じクラス名つけちゃっても、個々に解釈されるため大丈夫。って感じです。
その他にも色々機能があるので、サンプルコードで説明していきます。

サンプルコードで見てみる

基礎編
1. 通常パターン
DIVにスタイルをあてると、当然こうなる。

2. Shadow DOMを使って、見た目の情報を隠す。
要素に生えている「.createShadowRoot()」を実行すると、見た目の情報はShadow DOMを見に行くようになる。
(今回の場合、Shadow DOMに何も要素が入っていないので、外側で定義されているborder等のみ表示されている。

3. Shadow DOMに要素を入れていみる。
「.createShadowRoot()」で生成されるオブジェクトに対して、要素を入れると表示される。

4. いちいちcreateElementなんてやってらんないよ!ってことで、HTML Templatesを使う。
templateタグで要素を作成し、JavaScriptでAppendする。


Content編
5. contentタグを使って、「<div id="test1">test1</div>」で記述してた「test1」というテキストを復活させる。
上記のサンプルで表示されなくなっていた「test1」(言い換えると、Shadow DOMを用いてる要素のinnerHTML)は、contentというタグを挿入ポイントとし、代入してくれる。


6. contentタグを複数使ってみる。
contentタグに「select="セレクタ"」を指定することで、挿入する内容を指定することができる。(指定していない場合も混合できる)
※注意点: selectは直下にある要素しか選択することができない。こういう指定はできないってことです⇒[select="ul li"]


スタイル編
7. 【:hostセレクタ】Shadow DOMの「ページの他の部分から切り離される」というスタイルの当て方をする。
DIVにスタイルを当てていましたが、これをTemplate内へ移動します。
そして、「:host」というセレクタを指定することで、Shadow DOMのホスト要素に対して、Shadow DOMからスタイルを当てることができます。
また、「:host(.hogehoge)」と指定すると、ホスト要素にhogehogeクラスが指定されてた場合に適用されます。
(外側からスタイルを指定している場合は、外側が優先されるので注意。)


8. 【:host-contextセレクタ】Shadow DOMのホスト要素の状態でスタイルを切り替える。
id="test2"に対してもレイアウトを適用したいので、shadowDomTestという関数を作り、test1とtest2の両方にShadow DOMを適用しました。
しかし、サンプルのソースコードではtest2のみスタイルがあたっています。

これはtest2の要素の親にclass="hogehoge"と指定した為で、
「:host-context(.hogehoge)」と指定すると、Shadow DOMのホスト要素の親要素にhogehogeクラスが指定されてた場合に適用されます。
(:hostより優先度が高いので注意。)


9. 【::shadowセレクタ】Shadow DOMの外側からスタイルを当てる。
template要素内に「<span>test0</span>」を追加し、外部から「#test2::shadow span」でスタイルを指定すると、「<span>test0</span>」に適用されます。


10. 【::contentセレクタ】contentタグで挿入された要素に対してスタイルを当てる。
id="test1"の子要素の「<span hoge2>test1_2</span>」は、template要素内のcontentタグに代入されます。
content内の要素に対してスタイルを当てる場合は「::content [hoge2]」を指定します。


11. 【/deep/セレクタ】 Shadow DOMのカプセル化を突き破ってスタイルを指定する。
「body /deep/ span」このように指定することで、「::shadow」と書いていた部分もスタイルの適用可能箇所となります。
(Shadow DOM以外の要素にも適用されます)


まとめ

このサンプルコードに記載したことを把握しておけば、PolymerのDemoコードも少しは読めるようになるはず。。と思います。
が、Polymer独自の拡張部分である、Layout属性だったり、{{ 値 }}みたいな双方向バインディングの仕組みをもってたりするので、
それはそれでドキュメントをシッカリ読まないと理解できないかもしれません。

以上!

Web Componentsを試してみる

【Web ComponentsとPolymerをさわってみる。】の続きですが、
今回はWeb Componentsで軽くサンプル作りつつポイントを書いていきたいと思います。
Polymerについては触れないです。

今回サラっと触っているのが、下記のAPIです。
Custom Elements
HTML Imports
HTML Templates
Shadow DOM

ちなみに、筆者の実行環境は【Google Chromeのバージョン 37.0.2062.120】です。 

サンプルコード

このコードは、ホバーすることで色の変わる【x-sample】というタグを作っています。
そして、コード中にある、8つの【※注目※】については下部で説明します。


  • 注目1
:unresolved
というCSSの擬似クラスが適用されるのは、Custom Elementsが未定義の場合。
サンプルコードを実行すると、<x-sample>は適用されておらず、<x-sample2>は適用されている。
<sample>は命名規則からズレているからか、適用されず。
「:unresolved」の使いドコロは、未定義状態の要素が見えてしまうことによって起こる表示のチラツキを抑えるとかになります。

  • 注目2
<link rel="stylesheet" href="sample.css">
でcssを読み込もうとしているが、読み込みされない。
http://www.w3.org/TR/shadow-dom/#inert-html-elements
図. 通信さえ走らない
直書きすれば問題はないが、それでは色々と辛いので下記のような手法がある。
Vulcanizeで減らすHTML ImportsのHTTPリクエスト

また、Polymerの場合は良しなにインライン展開してくれるらしい。

  • 注目3
ここでdocumentを参照すると、インポートした側の参照になってしまう。
インポートされる側を参照できるようにしておく。

  • 注目4〜7
4〜7で指定しているコールバックの実行タイミングは下記です。
実際に、Developer Toolsのconsoleから実行してみればよく分かると思います。
// 要素が作成されたタイミング
var sample = document.createElement('x-sample');
⇒ createdCallback 
// 要素の属性が変更されたタイミング
sample.id = "sample";
⇒ attributeChangedCallback 
// 要素がDOMツリーへ挿入されるタイミング
document.body.appendChild(sample);
⇒ attachedCallback 
// 要素がDOMツリーから削除されるタイミング
document.body.removeChild(sample);
⇒ detachedCallback

  • 注目8
Custom Elementsで使用できるタグの名前には、ハイフンが必須です。
下記の場合にはこんなエラーに。
document.registerElement('sample')
SyntaxError: Failed to execute 'registerElement' on 'Document': Registration failed for type 'sample'. The type name is invalid.

終わりに

Web Componentsネタは、ちょいちょい書くかもです!

以上!