Color programming tips with C++ samples: RGB24(16777216色)に対しHSL24は十分な分解能を持っているか?
今回は色の話。PCでは長らく16777216色を誇るRGB24がディスプレイはじめ色を取り扱う際の標準として利用されてきた。RGBなのかBGRなのかなどは今回の本題では無いのでスルーする。また、制限された環境におけるRGB16やRGB15であるとか65536色中同時256色であるとかシステムカラー固定8色であるとか、或いは一部特別な状況やデータ形式でRGB各14bitだとかHDRが…とかの話も今回は本題では無いのでスルーで(;´∀`)
『16777216色を誇るRGB24に対してHSL24は十分な分解能を持っているか?』
この問いに即答できるだろうか?ちょっと考えれば直ぐに答えが出るかもしれない。曰く、
YES
と。
確信を持ってYESと答えられたならどうぞ続きを読んで行って下さい。NOと答えられたなら読まなくても良いかも。でも本当かな?少しでも自信が無い方はどうぞ(´ω`)
RGB24 = R8G8B8 の情報量
色をR,G,Bの各色に分解し各8bitsの分解能で標本化して表す。通常はR,G,Bの各標本には単純に整数を用いる。よってRGB24が表す色の情報量は当然24bitsで、これは組み合わせの総数にすると pow(2,24) = 16,777,216 通り(≈色数)。
HSL の情報量
HSLを考える時、幾つかのパターンが考えられる。
H8S8L8 の情報量
RGB24に倣って最も単純に考え得る色のデータ構造はH8S8L8で各要素が整数のパターン。さて、これは本当に24bitsの色情報を持っているだろうか?
少なくともH8S8L8としては24bitsの情報量を持っている事は確かだ。でも、実際のPC上ではHSLをネイティブに扱うディスプレイデバイスは恐らく無くて、色が実際にディスプレイから発色されるまでのどこかでRGB24に変換されていると考えられる。これは本当に24bitsの、つまり16,777,216通りの色を扱えているだろうか?
この答えを求める為に HSL → RGB の色空間の変換アルゴリズムを知る必要がある。
ENのWikipediaのHSL色空間の記事は詳細で丁寧で、 HSL → RGB の変換アルゴリズムも掲載されている。これを基にC++で実装すると、
RGB24とHSL24は、
struct RGB24 { uint8_t r, g, b; };
struct HSL24 { uint8_t h, s, l; };
こんな具合にしておいて、変換アルゴリズムの実装は、
こんな具合に。
入力のHSL24では各8bitのフルレンジ[0-256)にH(色相≈色角度)、S(彩度)、L(明度)を割り当てる。変換アルゴリズムの参考としたENのWikipediaの記事ではHを弧度法で扱っているので [0-360)° を扱っているけれど、これは平面角の次元を持っていれば単位は何だって構わないので、 ラジアンを採用すれば [0-2π) rad でも構わない。
でも弧度法で0°から360°を1°の分解能として整数でカバーするには勿論8bitでは足りない(0..359の360通りの状態を表す為には log(360,2)≈8.492 bits の枠が必要)ので、弧度法を採用する意味は無い。また、ラジアンは少数を扱う必要があるので8bitで一般的な少数を扱う仕組みを組み込むのはちょっと現実的じゃない。それで例示した実装では H は 0..255 の整数に unorm (符号無し正規化値: 0.0-1.0)をマップする形で円周"度"みたいに扱っている。
また、この実装では変換アルゴリズムの内部で用いる浮動小数点数の分解能の都合でも変換による情報損失が発生する可能性があるから、とりあえず内部型の影響を変化させて観察しやすい様にテンプレート関数にしてみた。(実際にこれはIEEE754的に言うところのBinary32, Binary64, Binary128の3つのセットを使って実験してみたよ。)
と、言う訳で実験的に HSL24 → RGB24 が16777216色を出力できるのかやってみる。
実験ソース
先ほどの変換アルゴリズムに尾びれ背びれ胸びれなどを付けてみた。
最初に簡単な実装で…と思って書き始めてから、後でちょっとだけゴテゴテ付け加えてしまったのでちょっと設計的にどうなのと言う残念な気持ちになったであろう事は分かる。申し訳ない・w・;
内部型の浮動小数点数について
Makefileから3通りの実行ファイルが生成されます。 binary32 binary64 binary128 はそれぞれ変換アルゴリズムの内部型に float double __float128 を用いてビルドする様に仕込まれています。__float128はGCC4.6以降で使用できる拡張仕様で、気になった方は以下の日本語記事が参考になると思います。
.cxx にて ADOPT_BINARY32 ADOPT_BINARY64 ADOPT_BINARY128 の定義状態によりCPPで簡単なソースコードの特殊化が行われる様にしています。
Boost.GIL について
ちなみに Boost.GIL には HSL を扱うコードが今のところ含まれて居ません。また、このサンプルでは Boost.GIL はPNG形式の画像として結果を保存する為の libpng のラッパーとしてしか使っていません。(libpngとかCスタイル過ぎて使うだけでワヒャヒャヒャヒャヒャとかむず痒くなるよ!)。この程度の簡単な使い方であればLet's Boostの以下の記事を参考にすると良いです。
OpenMP について
ソフトウェア浮動小数点数となる __float128 を用いる場合、CPU組み込みで扱える float double に比べて著者環境でも凡そ 7 倍の計算時間が必要でした。具体的には float double が6秒弱で全体の処理を終えるのに対し __float128 は40秒少々を要しました。そこで、単純な処理ですから処理を平行化しておこう、となるわけです。
なるわけですが、C++11 threadを使うまでもない本当にちょこっと、の平行化なので気まぐれにOpenMPでプラグマ1行書いて済ませてみる事にしました。尾びれ背びれ胸びれと付けてしまったのでOpenMP採用に伴うコード量の増加は数行になってしまいましたが、OpenMPの基本はソースコード中で「①このコードブロックで並行処理させたいぞ!」と「②このforを適宜に分割して平行化してくれ!」を指示するだけでよしなに並行処理するバイナリーを生成してくれます。
-fopenmp を付けなければ通常はエラーや警告も出ずにOpenMPを使わないバイナリーも生成できますし、今回程度の緩やかな平行化のニーズにはぴったりです。ちょっとここ平行化しといたら嬉しいかな〜程度ではなく、やらなければいろいろと死んでしまうのだ!みたいな状況ではOpenMPでは平行処理の最適化の粒度やプログラマビリティが悪いけど、このお手軽さは良い・w・
OpenMPの入門の参考としては下記の日本語の記事が分かりやすく整理されていて良いかもしれません。
実験環境
- Softwares
- Hardwares
実験結果
duplicated
HSL24→RGB24の全単射の結果、重複したRGB値が得られていました。変換アルゴリズム内部の分解能に応じても結果が異なりました。重複数と失われた情報量は、
重複数 | 実質情報量[bits] | 色数[colors] | %損失 | |
---|---|---|---|---|
基準RGB24 | 0 | 24 | 16,777,216 | 0 |
binary32(387) | 537,899 | 23.952 988 | 16,239,317 | 3.206 1 |
binary32(SSE) | 577,789 | 23.949 439 | 16,199,427 | 3.443 9 |
binary32(SSE+387) | 835,358 | 23.926 316 | 15,941,858 | 4.979 1 |
binary64(387) | 581,879 | 23.949 075 | 16,195,337 | 3.468 3 |
binary64(SSE) | 676,795 | 23.940 595 | 16,100,421 | 4.034 0 |
binary64(SSE+387) | 646,115 | 23.943 342 | 16,131,101 | 3.851 1 |
binary128(387) | 717,945 | 23.936 903 | 16,059,271 | 4.279 3 |
binary128(SSE) | 717,945 | 23.936 903 | 16,059,271 | 4.279 3 |
binary128(SSE+387) | 717,945 | 23.936 903 | 16,059,271 | 4.279 3 |
こんな結果に。実行バイナリーの 387 SSE SSE+387 はGCCのオプション -mfpmath に対応します。こまけぇ〜事はいいんだよ!という方は(SSE)だけで見比べても構いません。これには計算精度の良し悪しの他に計算内容による偶発的な要素も関わりますので(´・ω・`)
ところでそもそも HSL24 は何色か?
3つのパラメーターを256通りに組み合わせられるのでHSL24のパラメーターの組み合わせの総数はRGB24のそれと同じで当然16777216通りです。RGB24では3つのパラメーターのうち1つでも異なる色同士は別の発色となるので16777216色です。HSL24ではどうでしょう?
色相 H の値を 0 rad = 0 ° → uint8_t(0) から 2π rad = 360 ° → uint8_t(255) とマッピングすれば 0 と 255 が同じ色相となってしまい、実装による損失となります。この点は 0 rad → uint8_t(0) から 2π → uint8_t(256) = uint8_t(0) とマッピングされる様に正しく実装すれば問題になりません。実装上は uint8_t では 256 は扱えないので、256という値を扱う部分が必要であれば、そこはuint_fast16_tを使うとかdoubleを使うなどして慎重に実装しましょう。
彩度 S と 明度 L は範囲の上下は文字通り両極端でループしませんので、こちらは 0 .. 255 に正規化してマッピングすれば良いでしょう。
さて、問題はこの S と L です。
彩度 S は 色相 H の発色の良さ、即ち彩度ですから、S=0の時はあらゆるHに対して同じ色を呈します。S=0の世界は灰色(グレースケール)の世界になります。よってHSL24ではS=0の時、HとLによる呈色の組み合わせはLのみに依存しL=0..255の256通りしかありません。(8+8=16)bits=65536通りの値の組み合わせの内、256通りの他は全て重複した同じ色を呈します。よってこの時点でHSL24はRGB24に対して65,280色少ない事になります。
明度 L は文字通り明るさで、L=0だと真っ暗で物体表面から光がカメラなり目なりへ届かない状態です。また、L=255だとカメラの光センサー素子なり目の受容体なりが完全に飽和してしまい色が分からない極めて眩しい状態です。つまり。L=0またはL=255の時は、あらゆるHとSの組み合わせに対して「真っ黒」または「真っ白」しか呈色しません。よってHSL24はRGB24に対して、 L=0 と L=255 の時、HとSによる各 65536 通りは各 1 通りしか呈色せず、Lを要因としても RGB24 に対し 65,535*2=131,070 色少ない事になります。
と、言う訳でHSL24は理論上RGB24に対し 65,280+131,070=196,350色少ない事になり、HSL24が呈色可能な色数は16,580,866色となります(HSL24=H8S8L8とした場合)。これはRGB24の持つ色空間よりHSL24の持つ色空間は 1.170 3 % 小さい事になります。
実際には今回のHSL24→RGB24の変換結果は3〜5%の損失が生じて居て、理論上の損失に加えて計算上の損失も大きく影響していた事が分かります。
記事が長くなって来たのでぷちまとめ
HSL24はRGB24に対して理論的に重複した呈色を含むため 1.1703% も色空間が狭い。加えてHSL24→RGB24の変換計算でも呈色の損失が生じ、全体としてRGB24に対してHSL24で呈色可能な色空間は3〜5%程度狭くなってしまう。
必要に応じて、対応策としてはHSL24のH、S、Lの各情報量をヘテロに構成(例えばH12S6L6とか)する方法、そもそもRGBより多い情報量をHSLの色情報の保持に割り当てる(例えばHSL32とかHSL96とか)が考えられる。それはまたおいおい記事にするかも。
GCC4.7.1: C++11 と OpenMP のちょっと惜しい関係
C++11から言語仕様のスレッディング周りが強化され、thread、atomic、mutexなどなど言語標準機能として扱える様になり、ちょっとやそっとやそれなりの用途でもintel TBBやBoost.Threadに頼らずにマルチスレッディングソフトウェアを開発し易くなりました。
なりました、が、ありがちなごく単純なforのちょっとやそっとの並行化如きであれば OpenMP で十分です。ところがC++11とOpenMPの組み合わせではちょっと惜しい部分があります。
OpenMP による for 並行化
とりあえず OpenMP による for の並行化ついて簡単に。
↑こんなソースを
g++ omp.cxx -fopenmp
とかビルドして実行すると、OpenMPにより0..9の出力が並行化されあばばばばばします。ちなみに-fopenmp
を付けずにコンパイルすればプラグマは活きずにOpenMPによる並行化は行われず、0..9を順序良く出力します。
C++11 for
さて、C++11では区間全域(いわゆるforeachタイプの)に対するforとして range-based for なる構文が実装されました。
std::vector<unsigned> v(10);
std::iota(v.begin(), v.end(), 0);
for (auto a : v)
cout << a;
こんなのですな。既に多くのC++erがこの構文に慣れて来た頃かもしれません。ところがこの構文は便利な OpenMP と組み合わせられません。
↑こんなソースを
g++ omp.cxx -fopenmp
とかビルドしようとすると、
omp.cxx: In function ‘int main()’:
omp.cxx:6:14: error: expected ‘=’ before ‘:’ token
omp.cxx:6:37: error: expected ‘;’ before ‘)’ token
omp.cxx:6:37: error: expected primary-expression before ‘)’ token
omp.cxx:9:1: error: expected primary-expression before ‘}’ token
omp.cxx:9:1: error: expected ‘)’ before ‘}’ token
omp.cxx:9:1: error: expected primary-expression before ‘}’ token
omp.cxx:9:1: error: expected ‘;’ before ‘}’ token
omp.cxx:6:3: error: invalid type for iteration variable ‘a’
とかなんとか激しく構文エラーを指摘されてしょんぼりする事になるでしょう(´・ω・`)
(´・ω・`)
今回は GCC 4.7.1 で試しました。
によればGCC 4.7系はOpenMP 3.1をサポートしているのだそうだけれど、
とか見てより新しい次のシリーズのOpenMP規格、OpenMP 4.0の仕様を見ても、C++はC++98を基準にしたまま。 range-based forをOpenMPでちょちょいと並行化できる日は遠いのかも(´・ω・`)
openSUSE: eyefi-config 011.20120712 package released
Link to OBS
なに?
OBSにパッケージがあった eyefi-config が随分古かったのでbranchして現行最新版のgit-reposより 2012-07-12 版に差し替えました。
で?
- --transfer-mode
- --wigi-radio
- --endless
などが使える様になってたり、 -p で渡すパスフレーズがバグる場合があるのが治ってたりします。
Qt5 / Qt Quick 2.0 第3回 続々・はじめたよ : Canvas
続々ですが今回はあっさりと終わっちゃいます。
教材
32:15くらいから。
概要
Qt5/Qt Quick 2.0でHTML5のCanvasを使うらしい。HTML5のCanvasはもう今更だから解説はしないけど、必要ならこちらとか参考にしながら進めたら良いと思うよ。
雛形
ビデオの34:30頃に今回の最初のソースが登場する。
解説
Canvas要素の前半のid、width、heightはただのRectangleにもあるQML部品の基本要素。描画タイミングのコールバック関数として onPaint プロパティにECMAScriptでHTML5のcanvasそのものなコードを書いている。
HTML5のcanvasやgetContext、それからコンテキストへの描画などについて知識が無ければ先にも紹介したこちらを見れば良いと思う。
Canvas
「あらゆるHTML5の2Dコンテキストピクセル操作をサポートしている」と明記されている。
Context2D
HTML5のcanvasの2DコンテキストもQMLのオブジェクトとしてきちんと定義されている。
ECMAScriptでgetContextして取得するオブジェクト。HTML5のそれ互換。
余談
プログラマーならHTML5がどうとかこうとかって話からそのcanvasについてあーのこーのと勉強するよりも、寧ろこのQt5/Qt Quick 2.0のCanvasでそれを学んだ方がドキュメンテーションやプログラマビリティについてよく整理しながらcanvasの本質をしっかりと学べるのではないかな、とか思うなど(次回のECMAScript講座からはいきなりこれで教えようかな、HTML講座じゃないしね。学生も楽しいだろうし)。
\(^o^)/
もうちょっと何かしら紹介するかと思ったんだけど、どうやらこのセッションではCanvasについてはこれだけ、そのあとはPathAnimationとTextのレンダリングとエフェクトの改善について軽くなったよって述べて、後はまとめ、質疑応答へ。
と、言う訳で私もいまさらCanvasにさほど興味無いので今回は短いけれどこれで終わりに。次回はQML要素をC++で作る紹介なんかを適当にやろうかな。
Qt5 / Qt Quick 2.0 第2回 続・はじめたよ : ShaderEffect
前回に続き、例の紹介ビデオを教材に、25分あたりからのシェーダーエフェクトをざっくり遊んでみる事にしましょう。今回は冗長な解説はせずに初出の事項についててきぱき解説するよ。
教材
- http://qt-project.org/videos/watch/whats-new-in-qtquick-2.0
- 25分くらいからのシェーダーエフェクト
対象
- QMLについてなんとなく雰囲気くらい分かった程度のさむばでぃ
- プログラミング一般の基礎知識はありそうなさむばでぃ
- GLSLとか触った事あるか基礎的な概要くらい知ってるさむばでぃ
雛形
教材にしたビデオの25:50付近で最初の雛形が登場します。
「しれっとロゴ画像ちゃうやんけー」などは気にしなくて良い事です。
解説
Row
内包する要素群をカラム要素として1つの行(=row)を成して表示する為のGUIコンテナー。今回はImageとShaderEffectの2つの要素を内包して使われている。
Image
画像をロードして表示するGUI部品。sourceはURLに対応。smoothはboolでデフォルトがtrueなので現在は明示的に指定している意味は無い。
ShaderEffect
他の何らかの要素(RectangleでもImageでも他適当な)にOpenGLにより頂点シェーダーとフラグメントシェーダーを適用して表示するエフェクター。シェーダープログラムの定義方法はGLSL。
頂点とフラグメントしか無いのが残念だけど、QMLで手軽にGLSLエフェクターも使えるのは嬉しいですね。今回のメイン。
property
これはC++による実装も含め、QMLを含むQtのプロパティシステムについて触れる必要が生じる。
とは言え、現段階で全貌を把握する必要はないので、今回登場するQMLでの property キーワードは要素にプロパティをQMLから追加またはオーバーライドする宣言であるという事だけ分かれば十分です。
variant
property宣言に続いて登場している variant は型名です。QMLではintやrealやstringなどの型システムがあり、QML要素のプロパティはC++にバインディングされていますから当然内部的には型があります。property宣言でプロパティを追加またはオーバーライドする為にはそのプロパティの型も指示する必要があり、property宣言とプロパティ名の間に今回はvariantと型名が入って居ます。
variant型は一般的なプログラミングにおける動的型付けでお馴染みのバリアント型だと思って置けば良いのですが、仕様には
The variant type is a generic property type. It is obsolete and exists only to support old applications; new applications should use var type properties instead.
曰く、「variant型はジェネリックなプロパティの型。これは廃止されており、古いアプリケーションらの為だけにサポートされている。新しいアプリケーションでは代わりにvar型をプロパティらに使ってね。」
と、いうわけで既に時代は variant ではなく var にすべきなのでありました。
var
あらゆるデータの型に対する参照が可能なQt新時代のバリアント型にしてECMAScriptとも互換な var 型です。
これからはvariantじゃなくてvarを使いましょう。
雛形・改
https://gist.github.com/4441640/042b94058b374f58162c04a215b5419cfc725119
Image::smoothのtrueはデフォルト値ですからソースコードでの明示は撤去
- 時代は variant じゃなくて var なので型の宣言を置き換え
GLSL を書きますよ
教材にしているビデオの26:30辺りでShaderEffectへしれっとGLSLがコピペされて登場します^^;
GLSLを知っているか、或いはHLSLでもCgでも良いのでシェーダー言語による基礎的なGPUプログラミングの知識があれば、なんという事の無い頂点シェーダーとフラグメントシェーダーです。
ここ数年の一般的なシェーダプログラムの実行方法ではC++レベルの通常のアプリケーションがOSで実行されるとOpenGLなどを介しGPUドライバーにシェーダープログラムを実行直前にコンパイルさせます。よってQML要素レベルでもGLSLプログラムソースをstringで定義する形式になっています。
まだ、実行しても特に先ほどの雛形から見た目の変化はありません。
GPUプログラミング
GPUプログラミングに詳しくない方の為に少しだけ解説すると、プログラマブルな世代の現代的なGPUでは基本要素として2つのプログラム構成部分が必ず用意されています。1つは頂点シェーダー、もう1つはフラグメントシェーダー(≈Microsoft系の世界ではピクセルシェーダーとも呼ばれます)。
GPUは元々CPUだけでは描画しきれない膨大な3Dグラフィックスを描画する為に進化した計算機の部品です。3Dグラフィックスの描画と云うのは、3Dのデータ(≈3Dモデルデータ)を特定のパラメーターで2Dの絵に変換して、それをディスプレイに接続されたフレームバッファーへ書き出す仕事が基本です。
通常一般のディスプレイは2Dですから、2Dの画像データを単に描画するのであれば、座標を指定して画素情報をコピーするだけです。しかし、3Dのデータと云うのは一般に画素ではなく形状を構成する点群(≈頂点群)とその点群をどの様に接続した面で物体が構成されるのかという約束事で構成されており、3Dの世界を2Dのディスプレイに描画する為にはこの様な3Dモデルデータを、どこから見ているのか(≈カメラ)を基にまるで撮影のシミュレーションそのものの様な処理を計算で行わなければなりません。綺麗な計算機画像を得る為には3Dモデルデータも数万個やそれ以上の頂点で構成されていたり、複雑な光線に関する計算が必要となり、とてもとても重たい処理で、CPUだけでやっていてはとてもとても計算時間が必要なものです。
そこで、CPUの様に汎用的に何でもプログラマブルに計算できる訳じゃないけれど、代わりに3Dグラフィックスに関する大量の計算には滅法強いGPUなる部品を作ったのです。
その流れは今でも勿論GPUプログラミングに影響、というかそのものとして受け継がれて居て、大量の3Dモデルデータの頂点群を特定のパラメーター(視点だとか)ではスクリーンの何処に映るのかを計算する頂点シェーダー、スクリーン上に転写された点群からそれら点群により構成される面に色塗りをするフラグメントシェーダーがGPUプログラミングの基本となっています。
現代的なGPUプログラミングでは、今回も用いられるGLSL(≈OpenGL)の他、HLSL(≈Direct3D)、Cg(NVIDIA言語)などC風の文法の専用言語を用います。ちなみにGPGPUはGPUの変態的な応用技術であってGPUは本来GPGPUを行う為に設計された訳ではありませんから、GPUで工夫した計算をさせようと、特にDirectComputeやOpenCLやCUDAなどそれ用に更に特殊化されたド変態言語やAPIを用いない限りはいろいろとGPUの特徴(というか癖というか生の仕様が透けて見えるというか)なプログラミングのスキルや知識が少なからず必要です。
です、が、GPUプログラミングの仕組みや概要が分かってしまえばなかなか楽しいものではありますので、他の部分はさておいて突拍子もなくGLSLで割と純粋にGPUプログラミングを楽しめるプラットフォームとしてQt5/Qt Quick 2.0が目の前に現れてくれているのですから、之を期にでも是非少しは遊んで見る事をお勧めします。(実際生のOpenGLやDirect3DではGLSLなどのシェーダー言語を書く他にもGPUを使う為のアレコレの初期化やシェーダープログラムのコンパイルなどの制御やそもそものGUIの制御などそれなりに煩わしいものです。)
余談
ちなみに、頂点シェーダーとフラグメントシェーダーに加えて現代のGPUプログラミングでは超ド変態な進化を遂げていて数年前から頂点データ群から別の頂点データ群を生成するジオメトリーシェーダーとかストリームアウトとか、サブディビジョンサーフェイス用にと実装が進んだテッセレーターがどうのとか、まあいろいろともっと進化した機能を使い熟す事も可能な、まあ兎に角ド変態デバイスとその取り扱いへと進化しています。著者もテッセレーターがどうした辺りからGPUをいじらなくなって久しいです。
解説
vertexShader
- qt_TexCoord0 に qt_MultiTexCood0 を代入しています。
- qt_MultiTexCood0 は attribute highp vec2 で頂点シェーダーに渡されて来る頂点属性の2つ目の値。
- gl_Position に qt_Matrix * qt_Vertex を代入しています。
- qt_Matrix は uniform highp mat4 でシェーダーに渡されている値。
- qt_Vertex は attribute highp vec4 で頂点シェーダーに渡されて来る頂点属性の1つ目の値。
attribute 各種はQMLのバックエンドの仕様に基づいて渡されてくる(はず)。少なくともQMLのShaderEffectを使う分には頂点属性は固定だと思って良い(はず)で、QMLのShaderEffectの頂点属性は { vec4 頂点座標, vec2 テクスチャー座標 } と推論できる。
と、いうわけで、日本語で読み下すと、
- テクスチャー座標をフラグメントシェーダーへパイプ。
- テクスチャー座標は頂点属性で渡されて居る値を引っ張り出す。
- スクリーン座標系へ変換した頂点座標をフラグメントシェーダーへパイプ。
- スクリーン座標系への入力頂点座標の変換はシェーダーに共通で渡されて居るバッファーから引っ張り出した4x4行列(≈ここでは3DCG用語で云うならば(ワールド×ビュー×スクリーン)変換行列)。
- 頂点の入力座標は頂点属性で渡されて居る値を引っ張りだす。
と、いうわけで、推測ですがQMLの元要素を4頂点の四角の座標+元要素を画像としてテキスチャーで転送されたデータがシェーダーにやってきて居て、変換行列の処理については2D以外等への応用を視野にしていて少なくともこの推測の元では単位行列でも入ってて、名目上の頂点座標処理を記述しつつテクスチャー座標と共に頂点座標をフラグメントシェーダーに出力(パイプ)している、のだと思います。
※実際ビデオを見続けると、そんな様な事の一部始終を解説してくれている。
GLSL tips 1
attribute は頂点シェーダーに渡って来る1つ1つの生の頂点データを変数にバインドする旨のGLSLのキーワード。HLSLで言ったらセマンティクスとかに近い辺り。
uniform は頂点毎などではなくシェーダープログラム全体に共通して渡されるデータを変数にバインドする旨のキーワード。HLSLで言ったらSM5の定数バッファー(HLSLの、だよ。GLSLのconstとかじゃないよ)の取り扱い的な感じ。
varying は頂点シェーダー→フラグメントシェーダーとパイプされるGPU内部での変数を宣言するキーワード。HLSLで言ったらシェーダー間のoutとinネ。
highp は C++風に言ったら long double 的な感じ。GPUネイティブな小数点数型(≠浮動小数点数型、ハードウェアによっては浮動とは限らない)にも精度の異なる型がサポートされていて、C++の float-double-long double みたいに一定の精度を区切りにGPUのハードウェアに依存した精度が lowp-midp-highp に割り当てられる。詳しはこちらを見ると分り易いと思うよ。
mat4 は 4x4 の行列型で、その前に highp とか付けて highp mat4 とか宣言して highp で構成された 4x4 行列の型という事になる。vec4 や vec2 も同様でこちらは4次元と2次元のベクター(≈要素次元N個を表すデータ配列)。
void main() { ... } は関数の定義で、mainは各シェーダーのエントリーポイント的な関数と思って差し支え無い。
fragmentShader
- 描画対象領域について2Dテクスチャーサンプラーでシェーダーに渡されたテクスチャー(GPU組み込みのテクスチャーサンプラー機能で要素の値を補完して取得できる)、シェーダーに共通で渡された不透明度を基に、サンプリングされた色に不透明度を掛け算して描画点の色を決定、出力している。
GLSL tips 2
sampler2D はGPUの内部機能の1つテクスチャーサンプラーを扱う型の宣言。テクスチャーサンプラーはC++風に言えば { return texture.get_pixel_rgba(texture_coordinate); } 的なアルゴリズムの関数でテクスチャーから要素を得るためのGPU組み込みの仕掛け、機能、と思えばOK。実際には一般に補完機能付きのアルゴリズムを用いる。
float は vec4 や vec2 風に言えば vec1 、つまり単一要素、スカラー値の型名です。
QMLからGLSLのuniformへ値を渡す
ビデオの29:50頃のコードでQMLからGLSLのuniformへと値を渡す手法についてデモを行なっています。
oh... わたしのアイコン使うんじゃなかったかしら…orz
解説
ShaderEffect::vertexShader はすっぱり無くなって居ますが、先程の内容がデフォルトなので無くても同様だったという訳。
さて、肝心のこの状態の内容ですが、先ずShaderEffect要素にQMLでユーザー独自のプロパティを追加して値をセットしてあります。
次に、ShaderEffect::fragmentShader のフラグメントシェーダーの定義中で uniform highp で先ほど定義したプロパティ名と同じ名前の変数を宣言しています。
これだけで QML → フラグメントシェーダー の値の受け渡しが出来ています。簡単で良いですね!
GLSL tips 3
GLSL(HLSLでもそうですが)では、基礎的な数学関数が言語組み込みのAPIで利用できます。 sin は正弦を求める御馴染みの三角関数ですね。今回は引数が float ではなく vec2 ですが、ベクターの要素毎に sin を求めたベクターが出力されるオーバーロードがあると思って差し支えありません。
アニメーションの適用
ぎゅうううんぎゅうううん…私のアイコンがー…orz
解説
ShaderEffectにQMLでtimeプロパティを追加しています。フラグメントシェーダーではこのtimeに依存する様に先ほどのsin関数の中を書き換えてあげたのみ。
レンダリングはQt5がよしなに回してくれて居るので再描画毎にシェーダーに渡るプロパティtimeの値が取得され、その値は NumberAnimation によって動的に生成されており、アニメーション(=時刻に応じた描画内容の変化)が実現できて居ます。
NumberAnimation on time
NumberAnimation on time は NumberAnimation なる関数オブジェクト的なQML要素をtime変数にバインドして居ます。
私のソースの書き方だとちょっとややこしいかもしれませんが、直前の property real time からの続きとしての定義ではなくて、 property real time はそれだけで値の定義は無いが変数の宣言としては完結しています(C++で言えば int value = 123; と定義を伴わずに int value; と宣言のみと言う事)。
NumberAnimation は ShaderEffect の単に子要素としてQMLで定義し、 on time と要素宣言に追加してある事で親要素の time の内容としてバインドさせて居ます。
NumberAnimation
QML要素のプロパティをアニメーションさせる事に特化した要素で、値を from から to へと評価時刻により変えて出力します。厳密な範囲としてはfromもtoも共に生成される値に含まれます(C++のrangeのbegin-endとは異なる)。
プロパティは from と to だけの様に仕様をチラ見しただけでは思えますが、きちんと SmoothedAnimation と SpringAnimation からの継承についても触れられて居ます。
duration は to から from への変化をマッピングする時間間隔を指定します。単位は ms (ミリ秒)。
loops は アニメーションの繰り返し方の指定で、デフォルトは 1 、つまり1回だけアニメーションしてループはしない。Animation.Infiniteについては後述。
from と to が含まれるのか確認したい?
こんな具合で小細工すれば、from の 0 も to の 1 も含まれてコンソールに出力される事を確認できます。
Animation.Infinite
Animation.Infinite は NumberAnimation の基底型でもあるAnimationに定義された定数。
loops に Animation.Infinite がセットされると stop() が明示的に呼び出されるまでアニメーションを繰り返してくれる。
非表示のShaderEffectの元要素を定義できる ShaderEffectSource
ビデオを続けると31:30頃にShaderEffect専用にそれ自体は表示しないQML要素の定義方法が紹介されます。
解説
ShaderEffectSource
ShaderEffect の source とする要素を定義するQML要素。動作はソースを見たまま予想通りで分り易いですね。
今回の結び
ShaderEffectを使うとQMLだけでもリッチなエフェクトを簡単に実装できてしまいますね。GUI ToolkitとしてもUI定義としてもECMAScriptベースのアプリケーションエンジンとしても強力なQMLにGPUパワーも少なくともエフェクターとしては、驚くべき事にバックエンドのC++コードに必要無ければ全く触れずとも多彩な表現力を発揮できる、と。
ビデオをもう少しだけ進めると次は "Drawing with JavaScript, based on HTML5 Canvas API" だそうで。明日も楽しみですね。…とは言え、そろそろC++コード書かないとなかのひとがうぐぅ…って感じではありますけれど・x・;
openSUSE-12.2 / Razor-qt : キーボードのリピート間隔などを簡単に設定する方法
状況
デスクトップ環境にKDE Plasmaを選択すればデフォルトでインストールされるKDEの設定パネルからキーボードレイアウトやリピート間隔を簡単に設定できる。
しかし、Razor-qtにはその様なコンフィグレーションツールが無い。
参考
pvanek曰く、
それ、QXKBでできるよ。
QXKB
※注: これではリピートは設定できない。
導入
http://software.opensuse.org/package/qxkb
zypper in qxkb
使用
レイアウト設定なんかはできるけど、お手軽お気軽なキーリピート設定はQXKBには無い。
この方法の参考
(´・ω・`)
xset
結局そうなるのね的な。
使用
xsetで、キーダウン開始より168ミリ秒後から48ミリ秒間隔でリピートする設定は、
xset r rate 168 48
とかシェルで叩けば良い。.zshrcなりなんなりに放り込んで置くと良い。