C++ ときどき ごはん、わりとてぃーぶれいく☆

USAGI.NETWORKのなかのひとのブログ。主にC++。

Qt5 / Qt Quick 2.0 第2回 続・はじめたよ : ShaderEffect

前回に続き、例の紹介ビデオを教材に、25分あたりからのシェーダーエフェクトをざっくり遊んでみる事にしましょう。今回は冗長な解説はせずに初出の事項についててきぱき解説するよ。

教材

対象

  • QMLについてなんとなく雰囲気くらい分かった程度のさむばでぃ
  • プログラミング一般の基礎知識はありそうなさむばでぃ
  • GLSLとか触った事あるか基礎的な概要くらい知ってるさむばでぃ

雛形

教材にしたビデオの25:50付近で最初の雛形が登場します。

f:id:USAGI-WRP:20130103165416p:plain

「しれっとロゴ画像ちゃうやんけー」などは気にしなくて良い事です。

解説

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を使いましょう。

雛形・改

GLSL を書きますよ

教材にしているビデオの26:30辺りでShaderEffectへしれっとGLSLがコピペされて登場します^^;

GLSLを知っているか、或いはHLSLでもCgでも良いのでシェーダー言語による基礎的なGPUプログラミングの知識があれば、なんという事の無い頂点シェーダーとフラグメントシェーダーです。

ここ数年の一般的なシェーダプログラムの実行方法ではC++レベルの通常のアプリケーションがOSで実行されるとOpenGLなどを介しGPUドライバーにシェーダープログラムを実行直前にコンパイルさせます。よってQML要素レベルでもGLSLプログラムソースをstringで定義する形式になっています。

f:id:USAGI-WRP:20130103211510p:plain

まだ、実行しても特に先ほどの雛形から見た目の変化はありません。

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風の文法の専用言語を用います。ちなみにGPGPUGPUの変態的な応用技術であってGPUは本来GPGPUを行う為に設計された訳ではありませんから、GPUで工夫した計算をさせようと、特にDirectComputeやOpenCLやCUDAなどそれ用に更に特殊化されたド変態言語やAPIを用いない限りはいろいろとGPUの特徴(というか癖というか生の仕様が透けて見えるというか)なプログラミングのスキルや知識が少なからず必要です。

です、が、GPUプログラミングの仕組みや概要が分かってしまえばなかなか楽しいものではありますので、他の部分はさておいて突拍子もなくGLSLで割と純粋にGPUプログラミングを楽しめるプラットフォームとしてQt5/Qt Quick 2.0が目の前に現れてくれているのですから、之を期にでも是非少しは遊んで見る事をお勧めします。(実際生のOpenGLDirect3DではGLSLなどのシェーダー言語を書く他にもGPUを使う為のアレコレの初期化やシェーダープログラムのコンパイルなどの制御やそもそものGUIの制御などそれなりに煩わしいものです。)

余談

ちなみに、頂点シェーダーとフラグメントシェーダーに加えて現代のGPUプログラミングでは超ド変態な進化を遂げていて数年前から頂点データ群から別の頂点データ群を生成するジオメトリーシェーダーとかストリームアウトとか、サブディビジョンサーフェイス用にと実装が進んだテッセレーターがどうのとか、まあいろいろともっと進化した機能を使い熟す事も可能な、まあ兎に角ド変態デバイスとその取り扱いへと進化しています。著者もテッセレーターがどうした辺りからGPUをいじらなくなって久しいです。

解説

vertexShader

  1. qt_TexCoord0 に qt_MultiTexCood0 を代入しています。
    1. qt_MultiTexCood0 は attribute highp vec2 で頂点シェーダーに渡されて来る頂点属性の2つ目の値。
  2. gl_Position に qt_Matrix * qt_Vertex を代入しています。
    1. qt_Matrix は uniform highp mat4 でシェーダーに渡されている値。
    2. qt_Vertex は attribute highp vec4 で頂点シェーダーに渡されて来る頂点属性の1つ目の値。

attribute 各種はQMLのバックエンドの仕様に基づいて渡されてくる(はず)。少なくともQMLのShaderEffectを使う分には頂点属性は固定だと思って良い(はず)で、QMLのShaderEffectの頂点属性は { vec4 頂点座標, vec2 テクスチャー座標 } と推論できる。

と、いうわけで、日本語で読み下すと、

  1. テクスチャー座標をフラグメントシェーダーへパイプ。
    1. テクスチャー座標は頂点属性で渡されて居る値を引っ張り出す。
  2. スクリーン座標系へ変換した頂点座標をフラグメントシェーダーへパイプ。
    1. スクリーン座標系への入力頂点座標の変換はシェーダーに共通で渡されて居るバッファーから引っ張り出した4x4行列(≈ここでは3DCG用語で云うならば(ワールド×ビュー×スクリーン)変換行列)。
    2. 頂点の入力座標は頂点属性で渡されて居る値を引っ張りだす。

と、いうわけで、推測ですが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

  1. 描画対象領域について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へと値を渡す手法についてデモを行なっています。

f:id:USAGI-WRP:20130103225431p:plain

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

※注: これではリピートは設定できない。

導入

使用

f:id:USAGI-WRP:20130103135314p:plain

レイアウト設定なんかはできるけど、お手軽お気軽なキーリピート設定はQXKBには無い。

この方法の参考

(´・ω・`)

xset

結局そうなるのね的な。

使用

xsetで、キーダウン開始より168ミリ秒後から48ミリ秒間隔でリピートする設定は、

xset r rate 168 48

とかシェルで叩けば良い。.zshrcなりなんなりに放り込んで置くと良い。

Qt5 / Qt Quick 2.0 第1回 はじめたよ : ParticleSystem

Qtまじやばい・x・

と、言う訳で先ずは簡単な触りから、この素晴らしく魅力的な世界に入ってみたいと思います。先ずは少し古いですがQt Quick 2.0について、2011年のデモ映像の真似っ子をしてみる事に。

本項の前提知識等

  • 適当なデータ記述言語に関する基礎知識(例えばHTMLとかXMLとか)
  • ECMAScriptの基礎知識
  • 基礎的な端末の操作 or IDEの使用経験

※今回はC++力は使いません。また、パーティクルシステムについては知らなくてもデモを見てパーティクルの意味(粒子)を考えれば分かる程度の浅い内容です。

教材

準備

openSUSEじゃない人はまあ…がんばって探したりビルドしてね!

参考(著者環境)

  << qmake -v      
QMake version 3.0
Using Qt version 5.0.0 in /usr/lib64

  << qmlviewer5 -v
Qml debugging is enabled. Only use this in a safe environment!
Qt QML Viewer version 5.0.0

  << qtcreator -version 2>&1 >/dev/null | grep "^[^ ].*"
Qt Creator 2.6.1 based on Qt 5.0.0
(C) 2012 Digia Plc

  << g++ --version | head -n1
g++ (SUSE Linux) 4.7.1 20120723 [gcc-4_7-branch revision 189773]

  << uname -a
Linux LH-MAIN 3.4.11-2.16-desktop #1 SMP PREEMPT Wed Sep 26 17:05:00 UTC 2012 (259fc87) x86_64 x86_64 x86_64 GNU/Linux

※" << "は著者環境のプロンプトです。

やってみる

Qt Quick 2.0 を遊ぶ雛形の作成

QtCreator -> File -> New file or project
 -> Projects::Applications::Qt Quick 2 UI

これでUI専用(QMLソースのみでC++部分が無い)のプロジェクトができます。構成ファイル群は、

  • hoge.qmlproject
  • hoge.qml

こんだけ・w・

どちらもテキストファイルなのでエディターで確認してみると良い。.qmlprojectも読んだら分かる程度。.qmlはこれから遊ぶQMLを書いていくファイル。どうやって動作させるかと言うと、qmlviewer(著者環境ではqmlviewer5)で.qmlをロードするだけ。

  << qmlviewer5 hoge.qml

実際アプリを作り込む訳で無くQMLで遊ぶ程度にもこれで十分だし、ちょっとQt/QMLを試したり調べた感じ何か作り込む場合も独自のQML要素をC++で実装したものダイナミックリンクライブラリーとして用意して置き、後はUIデザインしながら必要機能をQMLでまとめるなんて事も可能っぽい。もちろん、全てのQML要素はC++で実装されているのでQMLは一切書かずに全てのQMLの機能をC++レベルだけで実装させる事も可能。通常は都合の良い様に折り合いを付けてC++に支えられしQMLを有効活用するのが良いでしょう!

パーティクルシステムで遊ぶ為のQMLの基本

今回の教材にするビデオの11分くらいからQt Quick 2.0のお話になります。真面目に全部聞いてもいいけど、15分くらいからのQMLのデモを見て、そこから続くパーティクルシステムについて遊んでみる事にしましょう。17分20秒くらいからお待ち兼ね、QMLをリアルタイムに編集してのちょっとしたデモが始まります。

最初に提示されたQMLソースをそのまま打ち込むと

となります。importはC#JavaPythonRubyなどの経験者ならピンと来た、だいたいそんな様なものです。ECMAScriptしか経験した事が無い場合は、HTML側で事前にlinkで処理する.jsライブラリーよりもちょっとイケてる仕様の何かそんな様なものだと思えば良いでしょう。C++erはモジュールうらやましすーとか思えばいいと思うよ!(C++1yだかその次に入れる気みたいな話をどこかで聞いたけど。)

後はデータ記述言語を何かしら、特にEclipseAndroid開発でXMLGUIを作ったり、.net向けにXAMLを扱った事がある人なら、或いはQt GUIを使った事がある人も.uiはXMLベースだ。何れこのQMLというのがなんとなくGUIの記述言語っぽい事は分かると思う。しかもソースを編集する気力が無くなるXMLと違ってとても本質に忠実でシンプルだ!(勿論、私はこれを見た時に先ず喜んだよ!)

シンプル過ぎるデータ記述言語を目の前にしてもすぐには喜ばないのがC++er達だと思う。「しょぼいんじゃ…拡張性とか…」などとシンプル過ぎる事に懸念を抱くかも。でも大丈夫、この Rectangle { } などのQMLのGUI部品の1つ1つは内部的にはC++のクラスで実装されているし、もちろん構造化され継承によるポリモルフィズムにも基づいているし、それらはユーザーコードでオーバーライドも、或いはほぼ完全に空っぽの要素から派生してオレオレQML要素を作る事だってできるし、widthやheightの様なプロパティを持たせQMLへpublicに公開する事も簡単なんだ。(まあそれはまたおいおい。)

そして更に、QMLの優れている点は(これらは何もQML 2.0からの話じゃなくて1.xからそうみたいだけど)、QMLではECMAScriptが使えるって事。console.logも実装されているからprintfデバッグをQMLで行う事も簡単。

f:id:USAGI-WRP:20130102183601p:plain

絵がどうみてもビデオと違う?いちいち用意するのが面倒だったので最近ちょっと変えた私のアイコンをパーティクルの画像にしてみた。確かにパーティクルシステムのスプライトレンダリングには向いていないけど、本質じゃないから今回は気にしないで。

それと、参考としてVim用のQMLコードシンタックスハイライターは、

にあるよ。コードあるところにVimあり。

…さて、横道に逸れ過ぎないうちに、いい加減にビデオを先に進めてコードを書こう。

Rectangle、ParticleSystem、ImageParticle、Emitter

RectangleはQMLの最も基本的なGUIパーツ。日本語で言うと「四角」ってだけの部品。QMLでは一番外側の要素がウィンドウを構成する際にメニューやらウィンドウの装飾やらを覗いたGUI本体の枠になると思って良い。これは、別に一番外側がRectangleじゃなくたって構わないけれど、シンプルな、いわゆるHello, worldに相当する事をGUIでする時のお約束だね。

QMLの要素は要素ごとに固有または継承したプロパティを持っていて、width,height,colorはREADもWRITEも可能なプロパティ。それらの意味までは説明しなくても想像通り。colorが"black"と文字列を使っている点だけ注意かな。このcolorの部分については、

とか参考にどうぞ。これはQt 4.7の頃の記事みたいだけど、基本的には現役みたい。実際ちょっと桃色にしてみようか。

f:id:USAGI-WRP:20130102191027p:plain

ちなみに色指定は"#ffcccc"みたいに16進数で#RRGGBB形式でも指定できる。但しこの記法で透明度を加えたい場合には#AARRGGBBとなる点に注意が必要。と、言ってもこの最も外側の枠に透明度を指定しただけじゃ、残念な事にウィンドウそのものを透過させる事はできないんだけどね。生憎と私はまだC++コードを使わずにウィンドウを透過させる方法を知らないので、今回そういった事は抜きでQMLだけで続けて遊んでみようかと思う。

さて、ここでプロパティの指定が早速ECMAScriptでQt.rgbaなる関数を呼び出している様子に気づいただろうか?さらにしれっと私はC++形式の行コメント(これはECMAScriptでも同様に使える)も使っている。実はこの部分、次の様にも書ける。

プロパティに代入していたのはECMAScriptの式で、コードブロックで書くこともできるし、console.logも使える。

でもこれはECMAScriptとはちょっと違うと思っただろうか?さて、どこが?

「コードブロックの内部の各ステートメントの末尾にセミコロンが無い事だ!」と思った人が居て、もしもその人が私のJavaScript講座の受講者だったのならノートを見直すべきだよ、それはECMAScriptの言語仕様にも省略可能が明記されているので違う。(すまない、これはちょっとした悪ふざけの引っ掛けだ。不慮の事故を起こさない為にもセミコロンは付けた方が良い事には勿論同意するよ。)

color: { ... }

の部分はもしもECMAScriptでRectangleが連想配列の定義であると枠に当て嵌めて見るならば、

function(){ ... }()

でなければ、全体を純粋なECMAScriptの文法的に見るならば color にセットされる値は { ... } というハッシュオブジェクトになってしまう。しかし { ... } はハッシュオブジェクトのリテラルにそぐわないのでもしこれがECMAScriptならば実行時に文法エラーを吐くだろう。

しかし、実際にはこれはまるでその場でラムダ式が定義され、プロパティは値を評価する度にそのラムダ式が評価される様な挙動を示す。実行してみればこのcolorが評価されたタイミングでconsole.logが評価され標準出力に何やら表示される。この様に、ECMAScriptを基としつつもエレガントにパワフルかつコンパクトでシンプルなのがQMLらしい。(この形容は私が勝手にしただけでQtの中の人がそう言ってるのを聞いた訳ではない。)

さて、 ParticleSystem についてはこれから弄って楽しんで概要を把握する。パーティクルシステムと言うのはビデオでも軽く紹介されて既に動いている様に、「世界には粒がたくさんありました。」と言う事を表現(主に今回の様に可視化、或いは科学的なシミュレーションでもしばしば用いられる)して見せる仕組みの事を言う。世界は画面、粒は…何らかの特徴を持って動く…粒だよ、うん、粒だ。今回は物理ベースでも何でもない単純な幾何学的な動きをする粒をビルボード方式でレンダリングするだけのシステムだけど、実際パーティクルシステムは2次元じゃなくても3次元でも構わないし、粒1つがビー玉でも原子でもボーリング玉でも、はたまた人でも雉でも兎でも、惑星でも、ブラックマター(いやよく知らないけれど)でも何でも良い。勿論、弾幕シューティングゲームだってパーティクルシステムとも言える。

さて、そんな概要としてのパーティクルシステムをQML要素として実現してくれているのがParticleSystem。ParticleSystemは内包するパーティクル(=粒)の特徴に基づいて、その粒をたくさん同時に、その世界の法則に従って運動させる系を実装してくれている。内部に今回のソースではImageParticleとして画像を表示する(いわゆるパーティクルをスプライトレンダリングする)粒子と、その挙動の定義をするEmitterが含まれている。

Emitterを調整して挙動を面白くしてみよう、と言うのが以降のビデオで続く。

…そうそう、忘れていたけど anchors.fill ってプロパティは要素を何かにアンカー(=錨)を打ち込んで、相対的に自身の位置を調整できる仕組みで、CSSで言えばposition:relativeを明示した親要素の中でposition:absoluteの子要素が親要素の右端や下端を"アンカー"にしてright:20とかbottom:20とかって位置決めをできるそういう機能。anchors.fill: parent では自身は親要素にピッタリ広がって張り付くよって事で、親要素と同じ表示粋を子要素に与えたい時に使う便利な指定だよ。

Emitterいじるよ〜

ところで、そもそもEmitterとは?電気デバイスに触れた事がある人ならピンとしたかもしれない、ベース、コレクター、エミッター、なんてね。そのエミッター、つまりパーティクルシステムにおける粒子の放射装置だと思ったら分り易い。ParticleSystemにParticleが出現する為にはParticleを生成して放射してくれる何かが必要、それがEmitter。

さて、現在のEmitterは anchors.fill:parent によって外枠の ParticleSystem に、そのParticleSystemもまた anchors.fill: parent で外枠の Rectangle の表示域に一致している。つまり装飾を覗いたウィンドウ全体。Emitter::sizeは生成するパーティクルの大きさの指示であってEmitterの大きさではない事には注意。また、現在は50となっているけれど、この単位は pixels 。"1cm"とか"12pt"とかは指定できないみたい。

さてさて、ビデオを続けて見よう。Emitterで遊んでいる。19分30秒くらいの状態のコードは、

(※注: 著者のミスで Emitter の width と height に ; がついていますが、これだと各プロパティが ; の後の評価対象を値としてしまう為にエラーではなく undefined が width と height に指定されてしまいます。undefinedをプロパティに指示する事はQMLのstateでも使う手前もあり文法的には合法ですが、このデモ用としては予想した挙動にならない結果となってしまうので ; は付けない状態で試して下さい。)

こんな感じ。Emitter自身の大きさを width:50 height:50 と小さくし、ついでに会場とデモの都合Rectangleの大きさも width:500 height:500 に調整し、Emitterの配置を anchors.bottom: parent.bottom としてRectangleの下端を基準に変更した状態。

f:id:USAGI-WRP:20130102203540p:plain

私のアイコンが size:50 で表示されまくっても大き過ぎてアレなのでこのスクリーンショットでは size:10 にしてある。

ビデオの19分50秒ほどではEmitter::speed(→これはQt5の正式リリース前に仕様が変更となり、現在はEmitter::velocityとなっている)を指定して生成される粒子に速度を与えている。ちなみにこの状態では粒子は等速直線運動しかしない。

f:id:USAGI-WRP:20130102204716p:plain

この辺りからは動画の方が実行の様子が分り易いと思うので動画も用意してみました。


Qt5/QtQuick 2.0 ParticleSystem test 1-1

Emitter::velocity に与えた AngleDirection (こちらはCamelCaseな点にも注意。RectangleやParticleSystemやEmitterと同様にね。)は方位による速度指定を行うオブジェクトで、 angle:300 は弧度法の degrees 単位で速度を与え、magnitude は pixels/sec 。angleについて気を付けなければならないのは、この世界は四角形の左上を原点として、右へX、下へYのスクリーン座標系だって事。だから第一軸のX軸と平行かつ+向きの方位をangle:0として上ではなくて下向きに回るのがangleの+。それで右上はangle:300になる。

AngleDirectionについてはこの後紹介するangle or magnitude への Variation の組み合わせはあるものの、基本的には angle と magnitude しか設定パラメーターはありません。もっと詳しくという方は下記リファレンスをどうぞ。

これに Emitter::lifeSpan:8000 を加えた状態が、

こんな感じ。教材にしているビデオとも同様ですね。Emitter::lifeSpanはEmitterが発生するパーティクルの寿命を ms(ミリ秒) 単位でセットします。これまではユーザーが明示的に指定してなかったのでデフォルト値である1000がセットされた状態となって居て、生成されるパーティクルは1秒の寿命となっていた訳です。この値を8000にしたのでパーティクルの寿命が8秒に伸びて居ます。

ビデオを続けましょう。20分50秒ほどから Emitter::velocity にセットしている AngleDirection に angleVariation:10 を加えたデモを行なっています。

AngleDirection::angleVariation:10 は AngleDirection::angle:300 と指定した速度方位に対して -10以上+10以下の揺らぎを与えます。AngleDirection::angleVariationのデフォルト値は0なので、ユーザーがセットしない限りは揺らぎは生じません。 仕様文書が気になる人は下記からどうぞ。

ビデオを続けます。21分25秒くらいからのデモでは、Emitter::sizeVariation:40 でEmitterが生成する粒子の大きさに -40以上+40以下の揺らぎを与え(正確にはsize/endSizeだったりしますが気になる人は仕様を確認してみてください)、さらにImageParticle::colorVariation: 1.0 によりR,G,B各チャンネルそれぞれに -1.0以上+1.0以下の揺らぎを与えています。

さらにビデオを続けると、ParticleSystemの中に (Gravity)http://qt-project.org/doc/qt-5.0/qtquick/qml-qtquick-particles2-gravity.html(重力効果)を加えています。

注意として、元のビデオの Gravity::acceleration はQt5リリースの前に Gravity::magnutude に名前が変更になっています。 また、元のビデオのままではパラメーター的に美しく挙動を再現出来ないので値は少々変更してあります。Gravityも比較的単純なプロパティしかありません。

angle は重力の向き、 magnitude は重力加速度で pixels / (sec * sec) 単位です。Emitter同様にGravityも自身の領域の大きさと位置も持って居るので、私のサンプルソースでは幅は親要素(ParticleSystem)と同じ、高さは親要素の1/3、設置位置はYを親要素の高さの1/25としました。なんだか立体感を感じる動きになりましたね。

ビデオではここからGPU機能を使うシェーダーエフェクトについての解説となりますが、この記事もだいぶ長ったらしくなってしまい、途中で休憩も挟んだせいか私の口調も不安定で読み難い状態になってしまっていたかと思いますので今回はここまでにしようと思います。内容は薄いけれど、QMLからQt5に興味を持つには十分な掴みとなる内容だったと思います。

references

Qt5 / Qt Quick 2.0 はじめるよ

Qtに何故もっと注目して使いこなせる様になって置かなかったかと悔やまれます。

昨年末にとあるでしのお手伝いにとQtをほんの少しですが私も勉強して、C++のライブラリーとしてのQtの様子はなんとなく掴めて居ましたが、それだけでは然程注目を継続したいとは思って居ませんでした。(これは私がGUI Toolkitとは少し遠いCUIやライブラリーやGPUプログラミングが主な為で、GUI Toolkitを日常的に扱う人ならばそれでも十分に興味を持ったでしょう。)

近頃話題になりつつあるQt5とかQt Quick 2.0についても然程には興味を持って居ませんでした。しかし、Raspberry PIのデモの中にQt5のデモを幾つか見つけて、大変な魅力に漸く気付きました。

Qtまじやばい・x・

と、言う訳でどんどんQt5にもQMLにも興味を惹かれ、これはしっかりと勉強して使いこなせる様にしようと。もちろん、QMLが素敵だってその事自体も十分な魅力だけど、それだけなら深く飛び込みたい衝動には駈られなかったのは間違い無い。ユーザーたるプログラマーはC++からQMLまで任意のレベルを任意に組み合わせてプロダクトを作れる、それが気に入った大きな理由。QML/Qt Quick 2.0ばかりに目が行きそうな素敵なデモだけど、これら全てC++で制御可能だしQMLでECMAScriptレベルでも制御可能だし、独自にカスタマイズしたQML要素もC++でしっかり作れるし、連携も簡単だし、ダイナミックリンクライブラリー化も簡単な様にQtCreator/qmakeが用意されているし、Qt枠外の仕組みだってC++だから何だって連携できるんだ、しかも低コストかつ柔軟に。

(※昔、C++/CLIに触れた時にも似たような事を思いましたが、あれは…ね。向かう先が無く極力使うべきではないとか言われてもうホントかわいそうな言語だった、ポテンシャルは大きかったのに…。最近名前と目的地を変えた様だけど、いまさらね…。)

Qtまじやばい・x・

具体的な「はじめたよ」編はこれからちくちく記事に。なお、Raspberry PIについても「あれ、GPUこんなに性能良かったの・w・」という興味は惹かれますが、それはまたウェアラブルでもやろうと思った際にでも。

memo: openSUSE-12.2; systemctl start fam.service が failed するのを解決する

状況

設置仕立てのcourier-imap-sslthunderbirdで繋いでみたら矢鱈ととんでも無い勢いで

Jan  2 03:45:16 LH-SVR5 imapd-ssl: Error: Input/output error
Jan  2 03:45:16 LH-SVR5 imapd-ssl: Check for proper operation and configuration
Jan  2 03:45:16 LH-SVR5 imapd-ssl: of the File Access Monitor daemon (famd).

と出力された。

famdとやらが問題なのだろう。しかし、

systemctl start fam.service

しても失敗し、挙句にログが

Jan 02 04:10:06 LH-SVR5 fam[4654]: Starting File Access Monitoring Daemon..failed

しか残らない。 /etc/fam.conf を見てもおかしな気はしない。

対応

/etc/init.d/fam を確認すると /usr/sbin/fam を実行している。そこで、とりあえず、

/usr/sbin/fam --help

すると、

fam, version 2.7.0
Use: famd [ -f | -d | -v ] [ -l | -t seconds ] [ -T seconds ] \
                                [ -p prog.vers ] [ -L ] [ -c config_file ] [-C]
        -f              stay in foreground
        -d              debug
        -v              verbose
        -l              no polling
        -t seconds      set polling interval (default 6 s)
        -T seconds      set inactive timeout (default 5 s)
        -p prog.vers    set RPC program number and version
        -L              local only (ignore remote requests)
        -c config_file  path to alternate configuration file
                          (default is /etc/fam.conf)
        -C              insecure compatibility

とかなんとか。-d付けて実行してみる。

/usr/sbin/fam -d

すると、

famd[4749]: log level is LOG_DEBUG
famd[4749]: read /etc/fam.conf line 13: "insecure_compatibility" = "false"
famd[4749]: read /etc/fam.conf line 20: "untrusted_user" = "nobody"
famd[4749]: read /etc/fam.conf line 28: "local_only" = "false"
famd[4749]: read /etc/fam.conf line 35: "xtab_verification" = "true"
famd[4749]: Setting untrusted-user to "nobody" (uid: 65534, gid: 65533)
Cannot register service: RPC: Unable to receive; errno = Connection refused
famd[4749]: can't register with portmapper. Please start a portmapper (for example rpcbind), then try again to start famd.

と。

解決

systemctl start rpcbind.service
systemctl start fam.service

memo: nginx-1.0.15 + passenger-3.0.12-2.1.2 / openSUSE-12.2

状況

openSUSE-12.2にnginx+passengerを仕込みたい。

準備

rubygem-passenger-nginx

zypper se passenger
 ...

など眺めつつ、

zypper if rubygem-passenger-nginx
 ...

を見ると rubygem-passenger-nginx-3.0.12-2.1.2 がopenSUSE標準のリポジトリーにも用意されていた様だ。入れる。

zypper in rubygem-passenger-nginx

/etc/nginx/nginx.conf ; http

nginxにpassengerを使うための基本的な設定を追加する。

 ...
http {
 ...
  passenger_root /usr/lib64/ruby/gems/1.9.1/gems/passenger-3.0.12;
  passenger_ruby /usr/bin/ruby;
 ...
}
 ...

先ずは上記2行のみ記述追加してnginxの設定チェックを行う。勿論、passengerの在り処やrubyの在り処は導入するバージョンやOSやディストリビューションによって異なる可能性があるから、そこはfindとかwhichとかして確認してから設定する事。

nginx -t

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful

問題無いらしいのでたぶん使える。

/etc/nginx/nginx.conf ; server

具体的にpassengerを使いたいserverを設定する。

 ...
server {
 ...
  location ... {
   ...
    root /srv/.../xxx/public
    passenger_enabled on;
    index index.html;
   ...
  }
 ...
}
 ...

passenger_enabledのみが本来の必須設定、但し事実上 root と index は固定みたいなものなので合わせてこの3行を追記、確認する。

設定を変更したらもちろんテストする。

nginx -t

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful

よろしい・w・

結果

systemctl restart nginx.service
systemctl status nginx.service

などしてnginxを再始動する。nginxにpassengerを設定したserverにアクセスする。

f:id:USAGI-WRP:20130101172154p:plain

これはxxxにChiliProjectを設置して置いた例。もちろんnginx-passengerでテストする前にWEBRICK(script/server -e production -p 12345)とか動作テストしてあったので、nginx-passengerも設定が完了すれば当然の様に動いてくれた図。

おまけ

どうも普段関わる気が無いのにちょっと便利そうなアプリなんかがあってrubyに関わると無駄に時間を浪費する、そんな時代もそろそろ過去になってきたかな。gemの動作も早くなった気もするし、bundleだとか他にもなんかパッケージの依存性解決する仕組みが実用的になって、バージョンセンシティブ過ぎてイライラしたりする時間も大幅に減ったし。gem周りのエラー表示なんかはもうちょっとリッチになった方がいいと思うけど。