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

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

UE4: ProceduralMeshComponent 入門

はてなブログよ、わたしは帰ってきた・w・ と、いうわけで早速本題に入りましょ。

UE-4.14 から導入された事になっている ProcedualMeshComponent ですが、いまひとつ公式ドキュメントでも最低限のエグザンプルとブループリントの情報しかまだ整理されていないようなので、C++による日本語の入門まとめを書いておこうと思います。

必要最小限の使い方: C++

↓とりあえずこんなのを出すエグザンプルとtips。

cppf:id:USAGI-WRP:20170526231022p:plain

1. MyProject.Build.cs

  1. PublicDependencyModuleNames.AddRange"ProceduralMeshComponent" を追加。

2. MyActor.h

一般的に Actor 系のヘッダーで UProceduralMeshComponentポインター型を使いたいだけならクラスの宣言だけ AActor クラスの宣言の手前に書いておけば十分。

class UProceduralMeshComponent;

class AActor で保持して使いたい事が多いと思うので UProceduralMeshComponent * のメンバー変数を宣言しておく。

UProceduralMeshComponent* mesh;

3. MyActor.cpp

とりあえず必要最小限、"さんかく1枚を実行時に作る"実装例。

ProceduralMeshComponent の中身を使うので素直にヘッダーを include

#include "ProceduralMeshComponent.h"

とりあえず、という事なら以下の実装を AMyActor::AMyActor でまとめて行っても「はじめてのさんかく」は表示できる。

AMyActor::AMyActor()
{
  // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
  PrimaryActorTick.bCanEverTick = true;
  
  // mesh の中身を作る。
  mesh = CreateDefaultSubobject<UProceduralMeshComponent>( TEXT( "generated-mesh" ) );
  // とりあえず RootComponent として mesh をつっこむ。
  RootComponent = mesh;
  
  // ここから ProceduralMeshComponent のオブジェクトにメッシュの中身のデータを作らせるコード
  // 頂点群
  TArray<FVector> vertices;
  // インデックス群
  TArray<int32> indices;
  // 法線群(今回は空っぽのまま)
  TArray<FVector> normals;
  // テクスチャー座標群
  TArray<FVector2D> texcoords0;
  // 頂点カラー群(今回は空っぽのまま)
  TArray<FLinearColor> vertex_colors;
  // 接線群(今回は空っぽのまま)
  TArray<FProcMeshTangent> tangents;

  // 「はじめてのさんかっけい」に必要な3点の頂点座標を生成。
  vertices.Emplace( 0.0e+0, 0.0e+0, 0.0e+0 );
  vertices.Emplace( 0.0e+0, 1.0e+2, 0.0e+0 );
  vertices.Emplace( 1.0e+2, 0.0e+0, 0.0e+0 );

  // UE は「左手座標系Z-top」の「反時計回り面生成」なので↑の頂点をこの順序で繋ぐと+Z向きに三角面1つを生成できる。
  indices.Emplace( 0 );
  indices.Emplace( 1 );
  indices.Emplace( 2 );

  // テクスチャー座標を設定しておけばこんなエグザンプルでも適当なマテリアルをセットして模様出し確認はできます。
  texcoords0.Emplace( 0, 0 );
  texcoords0.Emplace( 0, 0 );
  texcoords0.Emplace( 0, 0 );

  // UProceduralMeshComponent::CreateMeshSection_LinearColor でメッシュを生成。
  // 第1引数: セクション番号; 0, 1, 2, ... を与える事で1つの UProceduralMeshComponent に複数のメッシュを内部的に同時に生成できます。
  // 第2引数: 頂点群
  // 第3引数: インデックス群
  // 第4引数: 法線群
  // 第5引数: テクスチャー座標群
  // 第6引数: 頂点カラー群
  // 第7引数: 法線群
  // 第8引数: コリジョン生成フラグ
  mesh->CreateMeshSection_LinearColor( 0, vertices, indices, normals, texcoords0, vertex_colors, tangents, true );

実用化にあたって手始めにすると良いかもしれないこと

  1. CreateMeshSection_LinearColor 周りのメッシュ生成コードを generate_mesh メンバー関数に分離。
  2. AActor::AActor ではなく AActor::PostLoad AActor::PostActorCreated などを override して↑の generate_mesh を呼ぶようにする。
  3. 適当なイベントまたは Tick などでアニメーションさせてみる。

執筆現在の残念なところ

  1. インデックスバッファーを TRIANGLES でしか扱えないところ。
    • せめて TRIANGLE-STRIP には対応して欲しいなー。

その他 tips

  1. CreateMeshSection_LinearColor の他に CreateMeshSection でも同様にメッシュ生成できる。
    • CreateMeshSection_LinearColor: 第6引数 を TArray<FLinearColor> で与える版
    • CreateMeshSection: 第6引数を TArray<FColor> で与える版
  2. ここまでの知識だけでも実行時に頂点やインデックスが複雑に変形するメッシュは作れる。
    1. AActor::Tickoverride してアニメーションを定義可能にし、
    2. 毎フレーム目的の状態のメッシュを CreateMeshSection_LinearColor 等で生成する。
  3. UProceduralMeshComponent の応用機能(上記までの「必要最小限の使い方」で紹介していない、という意味の)
    1. UpdateMeshSection_LinearColor または UpdateMeshSection
      • トポロジーの変更を伴わないメッシュの更新」をするための機能。
      • アニメーションを実現したい場合は Create 系に代えて Update 系を使った方が高速に動作する。
    2. ClearMeshSection: セクション番号を指定してメッシュを消す。
    3. ClearAllMeshSections: すべてのメッシュを消す。
    4. SetMeshSectionVisible: セクション番号のメッシュの仮死状態の boolean な切り替えを行う。
      • この機能を応用すると単純な表示/非表示として使うほか、予め複数のセクションに生成したメッシュを順に切り替えるアニメーションなども実装できる。いちおう。
    5. IsMeshSectionVisible: セクション番号のメッシュの可視状態を取得できる。
    6. GetNumSections: メッシュが生成されているセクションの数を取得できる。
    7. AddCollisionConvexMesh: 凸形状コリジョンを追加できる。
    8. ClearCollisionConvexMeshes: 凸形状コリジョンを消せる。
    9. SetCollisionConvexMeshes: 凸形状コリジョン群を TArray<TArray<FVector>> で一度にまとめて設定(既存の設定群とは置き換え)できる。
    10. ProcMeshBodySetup: コリジョンデータ。
    11. GetProcMeshSection: セクション番号のメッシュの内部データを取得する。
    12. SetProcMeshSection: セクション番号のメッシュの内部データを直接設定する。
    13. Interface_CollisionDataProvider から継承した機能群: GetPhysicsTriMeshData ContainsPhysicsTriMeshData WantsNegXTriMesh
    14. bUseComplexAsSimpleCollision: シンプルな凸形状コリジョンを使う場合は false 、複雑なジオメトリーを使う場合は true を設定するフラグ。
    15. UPrimitiveComponent から継承した機能群: CreateSceneProxy GetBodySetup GetMaterialFromCollisionFaceIndex
    16. UMeshComponent から継承した機能群: GetNumMaterials
    17. UObject から継承した機能: PostLoad

参考

  1. Procedural Mesh Component in C++:Getting Started - Epic Wiki
    • 「さいしょのさんかっけい」の原型
  2. GitHub - SiggiG/ProceduralMeshes: Plugin with example procedural mesh actors and components
    • ProceduralMeshComponent の応用実装の参考になる
  3. ProceduralMeshComponent | Unreal Engine API Reference
    • ブループリントのリファレンスだけどC++erにも無いよりまし
  4. https://github.com/EpicGames/UnrealEngine/tree/release/Engine/Plugins/Runtime/ProceduralMeshComponent
    • 結局ソース読むのがいちばんはやい(特に執筆現在まだ丁寧なC++er向けの解説は無いようなものなので)

C++ tips: Boost で demangling

RTTIで型情報を対話的に可視化したいと思うと、C++ではデマングルしないと割と読めません。extern "C"しておくとか邪悪臭いですし。

ところがデマングリングは処理系依存に触れる事になるので、ユーザーコードで扱いたいものではありません。そこで処理系依存のデマングルをラップしたライブラリーレベルでユーザーコードからは使いたいと思うのが一般的なプログラマーの思考かと思います。

Boost.Units にあるよ・w・

例えば、

#include <boost/units/detail/utility.hpp>
using boost::units::detail::demangle;
auto demangled_name = demangle(typeid(0).name());

こんな感じで使うと、翻訳時に0の型はintになるのでdemangled_nameには"int"なるstd::stringが入る事になります。

もうちょっとゴチャゴチャ見てみましょう・w・

魔黒は便利なC++機能です。死者が出ない程度に有効に使いましょう。と、それはさておき、この例の実行結果は著者環境では次の通りです。

i --> int
Ss --> std::string
St6vectorIhSaIhEE --> std::vector<unsigned char, std::allocator<unsigned char> >
1XIiIfdjbDnEE --> X<int, float, double, unsigned int, bool, decltype(nullptr)>
PFivE --> int (*)()

著者環境は g++-4.7.1 / openSUSE-12.2 です。テンプレートが絡まない型はマングリングされた状態でも読めなくも…いやどうなんだ…程度な事もありますが、テンプレートが絡んでるともう型名読めません、デマングル必須です・w・

と、言う訳で今回は「処理系依存のdemangleとか直接書きたくないでござる」という場合にBoostの一角に含まれたデマングリング機能を使っちゃう方法の紹介でした。

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
    • AMD Phenom2 X4 (3.0GHz x 4 cores)
    • DDR2-800 8GB
    • intel SSD 330 Series 128GB

実験結果

duplicated

HSL24→RGB24の全単射の結果、重複したRGB値が得られていました。変換アルゴリズム内部の分解能に応じても結果が異なりました。重複数と失われた情報量は、

重複数実質情報量[bits]色数[colors]%損失
基準RGB2402416,777,2160
binary32(387)537,89923.952 98816,239,3173.206 1
binary32(SSE)577,78923.949 43916,199,4273.443 9
binary32(SSE+387)835,35823.926 31615,941,8584.979 1
binary64(387)581,87923.949 07516,195,3373.468 3
binary64(SSE)676,79523.940 59516,100,4214.034 0
binary64(SSE+387)646,11523.943 34216,131,1013.851 1
binary128(387)717,94523.936 90316,059,2714.279 3
binary128(SSE)717,94523.936 90316,059,2714.279 3
binary128(SSE+387)717,94523.936 90316,059,2714.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でHTML5Canvasを使うらしい。HTML5Canvasはもう今更だから解説はしないけど、必要ならこちらとか参考にしながら進めたら良いと思うよ。

雛形

ビデオの34:30頃に今回の最初のソースが登場する。

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

解説

Canvas要素の前半のid、width、heightはただのRectangleにもあるQML部品の基本要素。描画タイミングのコールバック関数として onPaint プロパティにECMAScriptHTML5canvasそのものなコードを書いている。

HTML5canvasやgetContext、それからコンテキストへの描画などについて知識が無ければ先にも紹介したこちらを見れば良いと思う。

Canvas

御馴染みHTML5canvasのQML実装。

「あらゆるHTML5の2Dコンテキストピクセル操作をサポートしている」と明記されている。

Context2D

HTML5canvasの2DコンテキストもQMLのオブジェクトとしてきちんと定義されている。

ECMAScriptでgetContextして取得するオブジェクト。HTML5のそれ互換。

余談

プログラマーならHTML5がどうとかこうとかって話からそのcanvasについてあーのこーのと勉強するよりも、寧ろこのQt5/Qt Quick 2.0のCanvasでそれを学んだ方がドキュメンテーションやプログラマビリティについてよく整理しながらcanvasの本質をしっかりと学べるのではないかな、とか思うなど(次回のECMAScript講座からはいきなりこれで教えようかな、HTML講座じゃないしね。学生も楽しいだろうし)。

\(^o^)/

もうちょっと何かしら紹介するかと思ったんだけど、どうやらこのセッションではCanvasについてはこれだけ、そのあとはPathAnimationとTextのレンダリングとエフェクトの改善について軽くなったよって述べて、後はまとめ、質疑応答へ。

と、言う訳で私もいまさらCanvasにさほど興味無いので今回は短いけれどこれで終わりに。次回はQML要素をC++で作る紹介なんかを適当にやろうかな。