Unity + Visual Studio で、Managed Plugin をデバッグする (Windows)
はじめに
前回、OpenCV を 必要な分だけ Native Plugin の DLL としてまとめて利用する方法について書きました。
C++のライブラリを使ってC++のコードを書いて、Native Plugin として利用できるのは良いのですが、Unity Editor での実行中に、C++側のデバッグができるんかい?という疑問がわきました。
今回はそのあたりを調べてみました。ついでにManaged Pluginのデバッグの仕方についても調べたのですが、分量の関係で Managed/Nativeで2回に分けることにしました。
環境
- Windows 8.1 pro 64bit
- Unity 4.6.0f3 pro
- Visual Studio 2013 Community
- UnityVS (2013)
今回もがっつり Windows + Unity Pro 限定な内容になっておりますがご容赦ください。
題材
今回の記事は、Live Training "WRITING PLUGINS" の作例に対してデバッグできるよう改変を加えています。プロジェクトの作り方、コードの書き方、ビルドの仕方はこちらを参考。
GitHubにあげました
記事はmanaged/nativeで2回に分けましたが、プロジェクトは一つにまとめてあります。
まずは Managed Plugin から
UnityVSでできるんじゃないか?と天啓を受けて調べてみたら、確かにありました。
Managed(C#) Plugin は 上記事に書いてあるとおりでできますが、目立ったところだけこちらにメモっておきます。ちなみに、最後のデバッグの開始部分は、上のリンクと少し異なることをしています。上のリンクの仕方でもできますが、本記事のやり方の方が少し楽なので紹介いたします。
Target Framework: .NET Framework 3.5
Unity の Mono が .NET で言うところの 3.5 相当らしいので、そうしておきます。これをやらなくてもDLLのビルドはできるのですが、プロセスにアタッチしようとした際にエラーが出て止まります。
厳密には Unity 3.5 .net SubsetBase Class Libraries
を選ぶのが正しい ようです。.NET と Mono は微妙に異なる部分もあるようですし。
PROJECT > PROPERTIES > Application > Target frameworks:
出力先を Assets/Plugins にする
いつも通りの $(SolutionDir)\(Configuration)\
な場所に出力したdllらをあとから Assets/Plugins
にコピーすればいいかと思ったら、VSプロジェクトでのファイル構造などデバッグに必要な情報が、DLLファイルと一緒に書き出される .pdb
ファイルに含まれているようで、ビルド→出力 の時点で、利用する場所に書き出される必要があるようです。
PROJECT > PROPERTIES > Build > (下の方)Output > Output path
ブレイクポイントを使ったデバッグ
あらかじめ Solutionの Properties から、現在選択しているプロジェクトが実行されるようにしておくのがおすすめです。
Unity Editorも起動した状態で、VSの方で今回デバッグしたいManaged Plugin のプロジェクトを選択し DEBUG > Attach Unity Debugger
を実行すると、下のようなウィンドウが開きます。もしここが空欄だった場合は、一度 Unity の方で Play しておくと、出るかもしれません。
これで該当の Unity の Project 名を選択すると、Unityに対してVSのデバッガがアタッチされ、待機状態になります。この状態でブレイクポイントを張って、Unity Editor に戻って Play します。確かに DLL 化された部分のコードでブレイクできています。
つづきます
と、Managed な Plugin のデバッグ法について整理してみましたが、実際にManaged Plugin を作るシーンが余り思いつかないので、今回は助走です。
本命の Native Plugin のデバッグは次回の記事で扱います。
UnityでOpenCVを Native Plugin にして利用する (Windows)
はじめに
UnityでOpenCVを使いたい動機と下調べについては前回の記事に書きました
UnityでOpenCVSharpをつかってOpenCVする。マルチスレッドにもしてみる。 (Windows) - 自習室
今回は、OpenCVを native plugin 化して使う方法についてまとめます、というか、 @hecomi 先生のをなぞった時に出た問題点をまとめておきます
環境
- Windows 8.1 pro 64bit
- Unity 4.6.0f3 pro
- OpenCV for Windows 2.4.10
- Visual Studio 2013 Community
参考にした記事
基本(ただしMac)
この記事で、@hecomi 先生が OpenCVを native plugin 化して使う方法についてまとめてくださっています。基本はこれですが、内容がMacでbundleな感じです。
Unity で OpenCV で作成したテクスチャをネイティブプラグイン経由で利用してみた - 凹みTips
公式の基本(ただし英語)
Windowsでのやり方は、公式のLive Trainingで簡潔に説明されていました。前半はC#でManagedなpluginの説明で、23分あたりから、C++で作る Nativeなプラグインの説明になっています。ランダム関数を回して適当に数値を返すだけのプラグインを例にしているので、英語ですがわかりやすいと思います。
Live Training: Introduction to Plugins - YouTube
dllを作ること自体は Visual Studio Expressでもできますが、作った native Pluginを使うことは、Unity Proでないとできません。このあたりはUnityの厳しいところですね。
応用 Windows8.1 で、DllNotFoundException への対処付き
また、 @hecomi 先生が別の記事で、OpenCVのスタティックライブラリをラップしたdllにする方法についてもまとめくださっています。この記事で通常のlibを使ってdllにするとDllNotFoundException が出る、と報告されていますが、私のところでは解消できましたので後ほど紹介します。
Unity と OpenCV を組み合わせて現実・仮想双方を加工した AR な世界を Oculus Rift 越しに覗いてみた - 凹みTips
やること
ここでは試しに、先述の Windows8.1 の環境下で、@hecomi さんの一つ目の記事でやられていた、 OpenCVでカメラキャプチャーしてウィンドウを開いて表示しつつ、Unity内でテクスチャとして利用する、をやってみようと思います。
手順
プロジェクトの作成
このあたりは、Unity - Writing Plugins を参考に。
- Visual Studioで
- VC++ / Win32 Application を
- Application type: DLL
- Additional Options: EmptyProject でつくる
OpenCV周りのプロジェクト設定をする
PROJECT > Properties からいろいろ下記のような設定します
追加のインクルードディレクトリ
OpenCV関連のヘッダを読めるようにします。適宜OpenCVをインストールした場所に読み替えてください
OpenCVのスタティックリンクライブラリを利用する
OpenCVをせっかくdllとしてラップするので、別途 OpenCVのdll群を使わなくてもよいようにしたいです。ユーザにOpenCVをインストールさせたり、完成したアプリのフォルダ内にdllをたくさん含んだ状態で配布するのはちょっといけてない感じです。
そこで、OpenCVからリリースされている <OPENCV_ROOT>\build\x86\staticlib
の .lib
ファイルたちを利用するように指定します。これを使って以下ので順でdllを作成すると、すべての .lib
ファイルが一つのdllに固められて、そのdllだけをユーザに渡せばいい状態になります。
ランタイムライブラリをマルチスレッド(/MT)に変更
ここは @hecomi さんの受け売りです。
DLL化のためのおきまりのコードを書く
#include <iostream> #define DLLExport __declspec (dllexport) extern "C" { DLLExport int GetRandom() { return rand(); } }
先ほど動画を貼ったLive Trainingでは、上記のようなコードで ランダムな数値をはき出すだけの機能を dll 化しています。同様の手法で@hecomiさんのこちらの記事では、OpenCVを使ってカメラをたたいたり、別ウィンドウで表示したり、Unityのテクスチャとして使えるようにしています。コードは @hecomiさんの記事を参考にされてください
DLL化のために必要なスタティックライブラリをリンクする
私の環境ですと、 @hecomiさんの記事 の通り だとライブラリが不足しているようで、unresolved external symbol
エラーが大量発生します。エラーは下記のようなものたちで、多くはOpenCVのDLL本体に含まれているもののようです。これらもリンクするようにして、最終的には以下のような感じになります。
このあたりは、こちらの記事様で勉強させていただきました
- 【OpenCV】スタティックリンクライブラリの使用方法 | イメージングソリューション
- c++ - openCV 243 using static libs error LNK2019 - Stack Overflow
ビルド→Unityで使う
BUILD -> BUILD <Project Name>
で、ビルドされ、ReleaseなりDebugなりのフォルダに .dll
ファイルが生成されます。
これを、Unityで Assets/Plugins/hoge.dll
の位置に置きます
dllで定義された関数をUnityで利用する際は、以下のようにDllをインポートし関数を宣言するコードを用意し、オブジェクトにアタッチして使います。これは先述のUnity公式Live Trainingでの例です。
using UnityEngine; using System.Collections; using System.Runtime.InteropServices; public class TestNative : MonoBehaviour { [DllImport("RandomNumberDLL")] private static extern int GetRandom(); // Use this for initialization void Start () { print("Native Random Number: " + GetRandom()); } }
同様に @hecomi さんの記事を参考に、dllに定義したOpenCVを使った関数を呼び出してください
その2:スタティックライブラリを使わない場合
先述の Unity公式 live Trainingと同じやり方になります。
通常のライブラリファイルをリンクする
ProjectのPropertiesで staticlib
フォルダを参照するように指定している追加のライブラリディレクトリを、 lib
フォルダを参照するように書き換えます。
ランタイムライブラリを /MD にする
システム環境変数にOpenCVのDLLのありかを通す
Path
に以下の二つを記入します (F:\develop\opencv\
の部分は、ご自身の環境に合わせてください)
F:\develop\opencv\build\x64\vc12\bin; F:\develop\opencv\build\x86\vc12\bin;
はじめ x64
のものだけを追加していたのですが、 dll を 32bit でビルドしてたため、DllNotFoundException が発生しました。 x86
のbinもPathに通したら、エラーがなくなりました。
ビルドなど以下略
ここから先は、staticlibを使う場合と同じです
最後に
これで、 @hecomi さんの記事のように、カメラ画像がテクスチャに貼れて、同時に別ウィンドウが開きカメラ画像が見られたらOKです。
@hecomi さんありがとうございました。
UnityでOpenCVSharpをつかってOpenCVする。マルチスレッドにもしてみる。 (Windows)
はじめに
顔認識させつつ、ビデオスルーが遅れていないことを示そうとしたら、思いがけずEXILEっぽくなりました。こんばんは。
動機
OVRVISIONをしばらく前に手に入れて放置していたのですが、何か遊ぼうと思い、OpenCVを使ってみたくなりました。カメラ画にエフェクトかけてみたり。
@hecomi 先生のこれがど直球。これこれ。
Ovrvision で色んなエフェクト試してみた - 凹みTips
下調べ
最終的にOculus Riftとつなぎたいので、Unityでやります。 UnityでOpenCVを使う口のすてきな紹介記事様がこちら
UnityでOpenCVまとめ - 株式会社CFlatの明後日スタイルのブログ
OpenCVSharpが使いやすそう。こまめにアップデートされてるので安心感もある
しかしパフォーマンス落ちそうな気がするのとOpenCVならC++で書きたい、ってのとで、Unityのnative plugin化して使うのも押さえておきたい
というわけで二方向からトライすることにしました。今回はOpenCVSharpを使ってみます。
環境
ソフト
- Windows 8.1 pro 64bit
- Unity 4.6.0f3 Pro
- OpenCV for Windows 2.4.10
ハード
- Core i7 2600K (Sandy Bridge)
- Memory 16GB
- NVIDIA GeForce GTX 560 Ti
3年前のミドルエンド、のつもりデスクトップです。
基本をやってみる
まずは @kaorun55 先生
これでとりあえずOpenCVSharpでカメラ画像を撮ってきてUnityで表示するとこまでできました
からの @kaorun55 先生!
せっかくだから顔認識とかしたいです。ここも @kaorun55 先生です。
OpenCVで顔検出した場所にUnityのGameObujectを追従させる
ありがとうございます、できました。
しかし、私の環境では 5~6 fps しか出ませんでした。これはつらいです。
顔認識のマルチスレッド化
認識に足を引っ張られて動画がかくかくなのはかっこわるいので、撮ってきた画像を表示しつつUnityのオブジェクトを描画するスレッドと、顔認識をするスレッドを分けましょう。
お勉強
C# のマルチスレッドの書き方を全く知らなかったのでそこの勉強からです
lockステートメントが大変便利ってことがわかった!
やってみる(本記事のメイン)
Post Position 【Unity】 スレッドを使う 記事様のやり方で二つスレッドをたて、 OpenCVで顔検出した場所にUnityのGameObujectを追従させる 記事様でやっているカメラ画像の取得と顔認識の処理をそれぞれのスレッドでやる、という方針です。
詳細はこのあたりのコードをご覧ください
UnityOpenCVSharp/FaceDetectThreaded.cs at master · izmhr/UnityOpenCVSharp · GitHub
一考
結局顔認識が 5~7 fps なのは変わっていません。
ビデオスルーがなめらかになったのは良かったのですが、顔認識が遅れているので、顔とは異なる場所にCubeが表示されてしまいます。「顔を隠す」などアプリ次第では、ビデオスルーを遅くしてでも顔認識と画像が同期している方が適切な場合もあるかもしれません。
逆に、遅い顔認識の結果を補間(予測)して使う、という方向性もあるかと思いますが、難しそうなのでまたいつか。
(おまけ) カメラ画像が崩れているのを修正
@kaorun55 さんの記事では、CVのカメラで撮ってきた画像をUnityのオブジェクトに貼り付ける際に、bmpにしたところで画像が乱れていました。これはOpenCVSharpのバグなのでしょうか…*1
試行錯誤の結果なので最適とはいえませんが、修正の仕方を見つけたので報告いたします。
Jpegを経由する
実際はばらばらの場所で行っていますが、行った処理だけ抜粋します
// カメラ画像取得 // static Mat capImage; video.Read (capImage ); // jpegのバイナリに変換 // static byte[] cvtImageJpeg; cvtImageJpeg = capImage.ImEncode(".jpg"); // テクスチャデータとする texture.LoadImage(cvtImageJpeg); texture.Apply();
これでテクスチャ内での画像ずれが無くなりました
さいごに
今回のコードはGitHubにあげてあります。
izmhr/UnityOpenCVSharp · GitHub
OpenCVSharp/OpenCV の dll のたぐいは @kaorun55 さんの記事 UnityでOpenCVを使う(Windowsアプリ) を参考に入れ直してください。
*1: ちょうど最新のリリースで Mat -> Bitmap 変換のバグの修正が入ったようなのですが、これが関係しているのでしょうか…? Release 2.4.10 (11 Nov., 2014) · shimat/opencvsharp · GitHub
JavaScriptでニュートン法を用いて三次方程式を解いて、ついでにグラフも描く
初めに
完成品
ここで動きます
動機
そもそもの目的は、楕円の法線を求めるために四次方程式を解くことだったのですが、まずは手始めに三次方程式を解くことにしました。結果、高校時代に勉強した内容を思い出して、懐かしいような気分に浸れて、変な楽しさがありました(笑)
解の公式を使えば良いのでは?
三次方程式も四次方程式も解の公式が知られていますが、たとえば三次方程式を解く「カルダノの方法」だと、
という式を解いて x = 4 を算出することが出来ないなど、汎用性の乏しさが知られています。
ニュートン法を使う
この手の計算では一般的に使われているらしいニュートン法を利用する事にしました。
三次方程式は実数解を最低ひとつは持つことが明かですので、まずはニュートン法で実数解をひとつ求めて、残りの解(虚数解も含む)を求めるような関数を作ります。
コード
コードはこちらに上げてあります。
izmhr/newtonCubicEquation · GitHub
場合分け
ポイントだけ解説します
計算をやりやすくするために、特徴的な係数や特徴的な解が得られる場合をあらかじめ場合分けします。やり方にはいろいろあると思いますが、今回のプログラムで採用している場合分けについて紹介します。
を解きます。
解に0 が含まれる場合
言い換えると、定数項 の場合。
を解くことになります。 これは一般的な二次方程式の判別式を用いて、残りの2解がどうなるかを場合分けします
とすると、
- なら、0に加えて異なる実数解が2つ
- なら、0に加えて、実数の重解1組
- なら、0に加えて、共役虚数解1組
がそれぞれ存在します。
解に0が含まれない場合は、ニュートン法を用いて、1つ目の実数解を求める
が、そこであたえた近似開始値が解そのものだった場合
この をそのまま解の1つ目の実数解として利用する
近似開始値が、極値をとるxであった場合
この場合、ニュートン法が収束しなくなるので、別の近似開始値 を使うよううながす。私のプログラムでは0.1加えるようにしているが、万能ではないのでご注意です。
ニュートン法で1つ目の実数解が求まった後
ここでも判別式を利用する。ここで利用する解のパタンを判別する式については、こちらのサイトが詳しいです
http://www2.odn.ne.jp/~aai55890/donnwa2/sanzihannbetu.htm
- は、 の判別式。グラフの形状の把握に使う。
- は、 の極値 に対して、 で算出される値で、これも判別式として利用する。x軸との交差回数の判定のために使います
詳細は先ほどのサイトに。
かつ のとき
ことなる実数解3つが存在する。1つ目の実数解を用いると、解きたい方程式は
と書き直すことが出来、二次方程式を解けば良いことになる。
かつ のとき
このとき、異なる実数解2つで片方は重解になる。 ここで、先のニュートン法で求まった実数解が重解の一部である場合と、独立した解である場合の2通りがあり得る。
ここで、 が、重解を持てば、 は単独の解であったということになりますが、 がニュートン法で求めた解であるため、導き出されるAやBも、誤差を含む値になります。従って、 が重解を持つかどうかの判別は、
ではなく
(ここで Threshは、十分に小さい値) という形で行うことになります。 これが満たされれば、 は重解を持ち、元の に加えてもう1解算出すれば良いです。
満たされていない場合は、
が、もう一つの解になります。
それ以外の場合
実数解は初めの1つだけで、残りは共役な虚数解を持つことになります
ウェブアプリとして仕上げる
機能
機能として、以下の様な物を実装しています
- 主要機能
- 可視化関連の機能
- 与えられた三次関数のグラフを描画
- 実数解をプロット
- グラフのズームの調整
- (おまけ) 手っ取り早く試せるよう、サンプルの関数を5つプリセット
CreateJS を利用
グラフの描画は、Canvasに対してCreajteJSを使って行っています。D3などの利用も考えましたが、最近よく使っていて手が慣れてたのでCreateJSを利用しました。
さいごに
など、結構やることが多くて、勉強になりました。インプットタグを使うのが意外としんどかったです。
実際は虚数解が必要になるようなケースはあまりないので、正しい制約下で必要な実数解を最短で算出することが求められるようなことの方が多いでしょう。まだほとんど知らないのですが、次は非線形最適化のツールなど使ってみようと思っています。
改善版 reverse_iterator 使用中のerase()の仕方
2010年5月の記事にコメントいただいた
reverse_iterator使用時のerase()の仕方 - 自習室
この記事に、メモリ衝突が起きるよ、というコメントをいただきました。
自分がこの記事を書く際に使ったコードが見当たらなかったので、改めてそれらしく書いてみました。確かに、イテレータの進め方など結構気を遣わないと、直ぐ見つからないところを叩いたりしてしまうクソコードでした。
不理解だったところ
reverse_iterator
が、通常の iterator
のアダプター(特定の目的のためにラップして使いやすくする)オブジェクトだ、ということを理解しておりませんでした。
このページの一番上の図と説明が分かりやすい。
std::reverse_iterator - cppreference.com
Reverse iterator stores an iterator to the next element than the one it actually refers to
リバースイテレータは、そのリバースイテレータが指している要素の(正順での)一個先を指している(正順)イテレータを保持している
と書いてある。関係性を式で表すとこうなる
&*r == &*(i-1)
そうすると確かに、erase
のために、現在見ている reverse_iterator
に対応する iterator
を取得しようと思ったら、 base()
で対応するイテレータを取得した後、一個戻す、が仕様上正しい操作だ、という気がしてきます。
hogelist.erase(--(hoge_ri.base()));
ここまでの内容を図解したのが下の図。
標準iteratorとしてerase()したあと、リバースイテレータはどこを指しているのか?
この点は自分もいろいろ混乱したので、図解します。
上でも書いたように、リバースイテレータは通常のイテレータのアダプタというかラッパなので、本体であるイテレータが指すものから上記関係式で導き出される対象を指しています。erase()した結果、通常イテレータの場所は変化していませんが、それに対応するリバースイテレータが、リスト上では一個前に進んだことになります。
従って、ri++
で(逆向きに)進んでいたとしたら、erase()
した時には、ri
は自動的に次の要素を指しているので、ri++
は不要、と言うことになります。
サンプルコード
charとint をメンバに持つクラスHogeのlistを作り、その中からユーザのキーボード入力で指定されたintの値を持つhogeを一個ずつ消していく、という内容です。このサンプルでは、特にリバースである必然性はありません。
そもそも2010年の元記事でリバースイテレータを使った理由は、OpenGLで描画をしながら、不要になったオブジェクトを消す、と言う操作を、1回の反復操作で行いたい、というものでした。OpenGLでは後から描いた物が先に描いた物の手前に上書きされるので、リストの作り方次第では、逆順に描画をコールしたいケースが発生します。
全文はこちら
リバースイテレータでリストを回しながら、 時折 erase を行う
大事なとこだけ抜粋します
while(hoge_ri != hogelist.rend()) { if(hoge_ri->check(value)) //当該Hogeインスタンスが value と同値のメンバを持っていたら { hogelist.erase(--(hoge_ri.base())); // 消す(と同時に進んでいる) cout << "###erase### " << value << endl; } else { hoge_ri->print(); // 持ってなかったら標準出力して hoge_ri++; //進める } }
先述の注意点の通り、消したときは、hoge_ri を ++ しておりません。
最後に
そもそもリバースイテレータを必要としない構造で書いた方が、可読性も上がるし良いよなーと思いました。
CreateJSで 2D 線分の交差判定
はじめに
2Dにおける物体どうしの衝突判定アルゴリズム、またはその一部として、線分同士の交差判定が必要になりました。
アルゴリズム
今回は、こちらの記事を忠実に実装させていただきました
線分ですので、
- 始点 + ベクトル(長さを持つ)
- 始点 + 終点
のいずれかが分かっていれば良いのでお気楽です。数回の外積計算でフィニッシュです
実装
コードはgithubにあげました
使ってるライブラリ
EaselはCanvasを使いやすくラップしてくれているライブラリです。愛しています。
Victorは、2Dのベクトル関連計算のライブラリで、長さを求めたり、内積外積を計算したり、回転指せたりお手の物です。それにベクトルを「定数倍」する関数が無かったので、足した、気がします。
staticな関数もあるvector2dの決まり手的ライブラリってどれなんだろう…教えてエロイひと
ポイントだけ
side.js (辺、の意のつもり) line15 が、公差判定の関数です
Side.prototype.crossing = function(_start, _end) { // http://marupeke296.com/COL_2D_No10_SegmentAndSegment.html var v = this.start.clone().subtract(_start); var v1 = _end.clone().subtract(_start); var v2 = this.end.clone().subtract(this.start); if(v1.cross(v2) == 0) return false; var t2 = v.cross(v1) / v1.cross(v2); var t1 = v.cross(v2) / v1.cross(v2); if( 0 <= t2 && t2 <= 1 && 0 <= t1 && t1 <= 1) { // crossing point var cp = _start.clone().add(v1.scalarMultiply(t1)); this.color = '#f33'; return {point: cp, distance: t1}; } else { this.color = '#fff'; return false; } }
_start
_end
は、交差する候補の線分の始点終点です。
変数名は、元記事様の変数名と合わせてあります。
線分同士が交差していたら、その {交点と、始点から交点までの距離} を返します。交差していなかったらfalseを返します。
サンプルプログラム
サンプルでは、三つの線分と回転し長さを変えられる線分を用意し、回転する線分の始点から見て、線分との交点の中で最も近い点を検出する、ような物にしてみました。
複数物体や複雑形状への衝突判定に利用するイメージです。
いじょうでふ
openFrameworks, Three.js, Processing のGLSL バージョン対応状況調査
はじめに
openFrameworksのセミナーでShaderに触れた
この週末、合宿で行われた openFrameworks のセミナーに参加してきました。
セミナー本体の紹介は @yumu19 さんが現場の雰囲気なども伝わってくる素敵な記事を書いて下さっています。
講義内容の紹介は、 @shu223 さんのブログが充実しています
このイベントについて、このほかにも計5本もの記事を書かれていますスゴイ!
このセミナーの中で、FrameBufferObjectの使い方、PostProcessのためのaddonの使い方など、Shaderに関わる話がたくさん出てきました。
Shader言語のバージョン対応状況について気になった
最近仕事でもThree.jsをつかったり、趣味でoFやProcessingを触っていたので、各開発環境ごとにびみょーに異なるGLの対応状況をまとめて置きたくなりました。それぞれ、
環境 | GLのライブラリ | プログラミング言語 |
---|---|---|
Three.js | WebGL | Javascript |
openFrameworks | OpenGL | C(C++) |
Processing | JOGL | Java |
という感じで、同じOpenGLではあるのですが、それぞれ違う言語ですし、さらにそれぞれのラッパでくるまれた状態になっています。 しかし、そのなかで実行時に解釈されるglslについては、おなじバージョンでさえあればほぼ同じ書き方で行けます。
これまでShaderにまじめに取り組んで来なかったので、これを機に勉強してみようかな、と思ったのですが、実は各環境ごとにどうもGLの対応状況が異なるようです。
上記3環境についてGLの対応状況を調査し、自分がどのバージョンでglslを書けば良いか判断するのが、本記事の主な目的になります。もちろん最終的には、やりたい表現や案件次第で選ぶことになりますが、手始めに手をつけて勉強するためのバージョンを選ぶ、という感じです。
前提知識
GL, GL ES, のバージョンの前後関係や glsl のバージョンとの対応は こちらのサイト様に綺麗にまとまっていましたので、参考にさせていただいております。
また、最新までは更新されていませんが、バージョンごとにどのような機能が実装されてきたか、について、こちらのサイト様が端的にまとめて下さっています。
このあたりを参考にしながら本記事を書きました。
環境
- MacBook Air 2012 Mid
- Xcode 6.0.1
- openFrameworks v.0.8.4
- Three.js r68
- Processing 3.0a4
oF, Three, Processing については、本記事執筆時の最新リリースを利用しています。
openFrameworks v.0.8.4
glInfoExample
公式のサンプルにglのバージョンを確認する exaples/gl/glInfoExample
というプロジェクトがあります。実行した後の画面はこんな感じ
メインウィンドウの一番上に "opengl version: 2.1" と出ていますが、これは「このプロジェクトが」2.1で動かされました、と言う意味で、ハードウェアやライブラリの対応状況を表した物ではありません。
隣には、出力されたtxtが開かれています。これは GL の拡張機能をロードするヘルパライブラリのglew が、「私ここまでなら行けますよ」と言っている物で、このoFに含まれて居るglew的には 4.1 まではいける、ということらしいと分かります。
実際に作ってみる: 素のプロジェクトの場合
ofのプロジェクトに、以下のコードを足して、GLの対応状況を出力させます。たとえば、main.cpp でOpenGLをセットアップした後に入れると良いと思います。
// main.cpp ofSetupOpenGL(1024,768, OF_WINDOW); cout << "Vendor :" << glGetString(GL_VENDOR) << '\n'; cout << "GPU : " << glGetString(GL_RENDERER) << '\n'; cout << "OpenGL ver. " << glGetString(GL_VERSION) << '\n'; cout << "GLSL ver. " << glGetString(GL_SHADING_LANGUAGE_VERSION) << '\n';
この書き方については、こちらのサイトを参考にさせていただき、最後の SHADING_LANGUAGE_VERSOIN
を追加しています。
ProjectGeneratorで出力した素のプロジェクトでこれをやると、以下の様な出力になります
Vendor :Intel Inc. GPU : Intel HD Graphics 4000 OpenGL Engine OpenGL ver. 2.1 INTEL-8.28.32 GLSL ver. 1.20
これは、glInfoExample が出力しているのと同じですね。
PCごとの最新GLを利用する
マシンが使っているGPUと、oFのバージョンによって、利用できるGLのバージョンが変わります。openFrameworksで最新のGLを使える様にするには、以下のコードを加えます
#include "ofGLProgrammableRenderer.h" int main( ){ ofSetCurrentRenderer(ofGLProgrammableRenderer::TYPE); ofSetupOpenGL(1024,768, OF_WINDOW);
これで新しいGLに対応したレンダラーを利用するようになります。この書き方については、oF公式のShaderチュートリアルが参考になります
こうした後に、先ほどと同様にGLのバージョンを確認すると、このような出力になります。
Vendor :Intel Inc. GPU : Intel HD Graphics 4000 OpenGL Engine OpenGL ver. 4.1 INTEL-8.28.32 GLSL ver. 4.10
注意点としては、GL3.0 以上を使う場合、 GL2系で使われていた varying などの記述が使えなくなり、実行時にエラーが出ます。(glslコードのコンパイル時にエラーが出ます) 多くのサンプルプログラムが動かなくなります。
oFの場合、最新を使うべきか、GL2系にとどまるべきか
oFでシェーダを活かしたナイスなAddonに ofxPostGlith や ofxPostEffect を、参加したOFセミナーで教えていただきました。これらは共に GL2系のglsl で書かれています。
- ofxPostGlitch/example at master · maxillacult/ofxPostGlitch · GitHub
- neilmendoza/ofxPostProcessing · GitHub
oF公式のShaderチュートリアルでは、GL2、GL3、ES2 がすべて添付されて、実行環境ごとに最適な物に切り替えられるようサンプルが作られています。
- 記事:http://www.openframeworks.cc/tutorials/graphics/shaders.html
- コード: openFrameworks/tutorials/shader at master · openframeworks/openFrameworks · GitHub
常に最新についていった方が将来幸せですし、最近話題のMRTを使ったDeferred Renderingなんかも視野に入れると、早めにGL4.0な書き方に移行していくのが望ましいのですが、学習中の身としては、サンプルの多いGL2.0系が捨てがたいです。
Three.js r68
WebGLは OpenGL ES 2.0 をベースにしています。GL ES 2.0 はOpenGL本筋で言うと GL2.0 相当なので、2004年リリースと、結構古いです。OpenGLの歴史についてはWikipedia参照。
このことは以下の様な手順で確認出来ます
- Three.js のサンプルから適当なページを開いて
- Developer Tools を開き
- rendererの初期化後の箇所にブレイクポイントをはってリロードして止めて、
- Consoleで以下の様なコマンドを叩くことで確認出来ます
_gl = renderer.context; // WebGLのコンテキストを取得する //WebGLRenderingContext {drawingBufferHeight: 150, drawingBufferWidth: 300, canvas: canvas, activeTexture: function, attachShader: function…} _gl.getParameter(_gl.VERSION); // WebGLのバージョンを確認する //"WebGL 1.0 (OpenGL ES 2.0 Chromium)" _gl.getParameter(_gl.SHADING_LANGUAGE_VERSION); //シェーディング言語のバージョンを確認する //"WebGL GLSL ES 1.0 (OpenGL ES GLSL ES 1.0 Chromium)"
実際にやるときはこんな画面です
ちなみに、上記のやりかたを見つける上で、この資料が役に立ちました。WebGLで利用できる関数や定数が一覧になっています。
WebGLでは今のところ選択肢無し
WebGL 2.0 で GLES 3.0 相当になる、と言うのが、ドラフトで発表されていますが、それも今年8月の話なので、(少なくともThree.jsは) しばらくはWebGL 1 (つまりES2.0相当) のつもりでいれば良いかな、という認識です。
Three.js のシェーダのサンプルが非常に充実している件
Three.js のShaderをつかったサンプルは、ただシェーディングだけでは無く、gpgpu的な使い方をした物もあり、非常に参考になります。これらを教材(やパクリ元)として使わない手は無いので、その点で ES2.0 にとどまる大きなメリットがあると言えます。
Processing 3.0a4
Processing follows the specification set by OpenGL ES,
公式にはこのように書いてあります。 Processing 3.0 として配布されているサンプルをいくつか覗いてみたところ、varying や texture2D といった記述があるところから、 たしかにES 2.0 つまり glsl 1.0 相当であることが確認出来ます。
- Processing 公式のShader解説
Shaders \ Processing.org
ただし、GL3.0 以降に対応していないと言うわけではなさそうです。
Processing で GL3.0 以降を使ってみる
このフォーラムが参考になりました
このフォーラム下の方に記載されているサンプルは、そのままでは動きません。PJOGLのプロファイル(詰まり、利用するGLのバージョン)を切り替える必要があって、そのためには、以下のコードが必要です。
// glsl150test.pde import javax.media.opengl.GL3; {PJOGL.PROFILE = 3; } // PJOGL内部でProfile=3と指定すると、GLES3.0が利用される Pshader shader; void setup() { // 以下略
グラデーション出塗りつぶすだけの超簡単な物ですが、実働するサンプルを github に上げました。
ナイスな作例
こちらで実際にgithubに上げられている作例は非常に良い感じです。
- 20面体をテッセレーションで細かく再分割していくサンプル
- ジオメトリシェーダで分割をするサンプル
このあたりは、ES 3.0 を利用して実現しているようです。
しかしやはりES2.0がまだまだ隆盛
とまぁProcessingでも ES3.0 が使えることは確認出来たのですが、公式のサンプルや下記のイケてるチュートリアルも ES 2.0 系で書かれているので、 3.0以降に移行するメリットは今のところ(僕には)少なさそう、という印象。
おまけのAndroid
実際にAndroidでGLばりばりな開発をしているわけでは無いのではっきりとしたことは言えませんが、Android の次のバージョン Android L で、OpenGL ES 3.1 をサポートすることになっているようです。 OpenGL ES 3.1 は、本家OpenGL 出言うと OpenGL 4.4 / GLSL 4.4. 相当のようです。
iOS はよくわからないっす
まとめ
結論としては、私は暫く、GL 2.1 / glsl #version 120 な書き方をしようと決めました。理由は、多くのサンプル・公開されている作品が、未だGL 2.1 , ES 2.0 系なシェーダ言語で書かれているからです。しかし一方で、あれこれ読んでみた結果、多少の接頭辞を変換すれば 3.0 以上へ対応するのも、めちゃくちゃ大変というわけではなさそうな印象を持ちました。
まずは、Vertex Shader, Fragment Shaderの基本を、2.1系で学び、MRTやテッセレーションなどの技術が必要になったら、改めて 3.0 以上に移行していこうと思います。