Lens パッケージの型の関係について

Lens パッケージの型の関係について

はじめに

たまに Lens を使うことがあって便利なんだけどよくわからないまま雰囲気で使っているところがあって、もう少し理解したいなという気がしていました。

なかなか難しくていまだに大半は理解できてないんですが Lens の型の図の意味が少し分かってきた気がするのでそれについて書きます。

使い方などについてはこの記事では触れません。

関係図

https://github.com/ekmett/lens/blob/b6a237453192f2b464f17e02929b57542a936b14/images/Hierarchy.png?raw=true

こういう図があってオブジェクト指向のクラス図のような感じなんですが、 Haskell にはクラスも継承もないのでこの矢印はどういう意味になっているのかよく分かってませんでした。

型の関係

Lens と Traversal を比べてみます。

type Lens s t a b = forall f. Functor f => (a -> f b) -> s -> f t

type Traversal s t a b = forall f. Applicative f => (a -> f b) -> s -> f t

図だと Lens の方が下にあって Lens から Traversal に矢印が伸びています。

しかし上の型を見ても二つがどういう関係にあるのか自分にはすぐ理解できませんでした。

比較1

簡単な例から考えてみることにします。

type E1 a = forall f. Functor f => f a

type E2 a = forall f. Applicative f => f a

E1 は Functor 制約が付いていて、E2 には Applicative 制約が付いています。

http://hackage.haskell.org/package/base-4.12.0.0/docs/Control-Applicative.html#g:1

Functor は Applicative のスーパークラスになっているので

  • Applicative であれば Functor でもある
  • Functor は Applicative ではない場合もある
  • E2 a の値は E1 a の値でもある
  • E1 a の値の数は E2 a の値の数より多い

のような感じのことが考えられると思います。

こう書くのが正しいかわかりませんが E2 aE1 a の部分集合になっていると思います。

$$ E1 \ a \supset E2 \ a $$

比較2

次に関数の引数に制約が付いている場合を考えてみます。

type E3 a b = forall f. Functor f => f a -> b

type E4 a b = forall f. Applicative f => f a -> b

E3 は Functor 制約の付いた引数を受け取る関数、E4 は Applicative 制約の付いた引数を受け取る関数です。

Functor の値の方が多いので E3 の方が入力となる値が多く、 E4 のほうが入力になる値が限定されていて関数が満たす必要がある要求が小さいというふうに考えられます。

この場合は比較1の例と逆に E3 a bE4 a b の部分集合になります。

$$ E3 \ a \ b \subset E4 \ a \ b $$

比較3

制約が付いていない引数が付いた場合です。

type E5 a b = forall f. Functor f => a -> f b

type E6 a b = forall f. Applicative f => a -> f b

この場合は E5 と E6 の入力になる値は同じだけあって、関数が満たす必要がある要求は変わりません。

関係を見る上ではこの引数は無視できるので比較1の場合と同じになります。

$$ E5 \ a \ b \supset E6 \ a \ b $$

比較4

Lens と Traversal の場合に戻ります。

type Lens s t a b = forall f. Functor f => (a -> f b) -> s -> f t

type Traversal s t a b = forall f. Applicative f => (a -> f b) -> s -> f t

無視できる引数を消してみます。

type Lens_ t b = forall f. Functor f => f b -> f t

type Traversal_ t b = forall f. Applicative f => f b -> f t

引数と戻り値の型の両方に制約が付いているんですが、この場合は引数と同じ制約を付けた型を返す関数なので引数の方だけ見るということでいいと思います。

結局 Lens と Traversal の場合は比較2と同じように考えられると思います。

Lens は Traversal の部分集合になっているようです。

$$ Lens \ s \ t \ a \ b \subset Traversal \ s \ t \ a \ b $$

まとめ

矢印は部分集合の関係にあることを表していると思います。

Lens の図の上の方に行くほど関数の入力が限定されて関数は作りやすくなり、集合に含まれる関数は多くなります。

下の方に行くほど関数の入力になる値が多くなり、関数は作りにくくなり、集合に含まれる関数は少なくなります。

一番下にある Equality は他の型の全ての部分集合になっていて、Equality の値である id は他の全ての型として使ったり、合成することができます。

OpenGL のユニフォームブロックについて

はじめに

OpenGL のシェーダにデータを受け渡す方法の一つにユニフォームがありますが、 ユニフォームを少し拡張したような機能でユニフォームブロックというものもあってこれについて調べたことについて書きます。

仕様

ユニフォームブロックにはユニフォームバッファオブジェクトを使うことでまとめてユニフォームを受け渡すことができます。

ライトやカメラの情報など複数のシェーダで使う情報を受け渡すときに便利なのではないかと思います。

しかしバッファにユニフォームのデータを書き込むときにはユニフォームブロックのメモリレイアウトについて考慮する必要があって、この部分が少し難しく間違いやすい気がします。

メモリレイアウトの種類として shared, packed, std140 があります。デフォルトは shared のようです。std140 を使うことが多いと思うのでこれのみ調べています。

OpenGL 仕様の 7.6.2.2 Standard Uniform Block Layout のあたりにかかれていました。

https://www.khronos.org/registry/OpenGL/specs/gl/glspec45.core.pdf

std140 のメモリレイアウト仕様の引用

  1. If the member is a scalar consuming N basic machine units, the base alignment is N.
  2. If the member is a two- or four-component vector with components consuming N basic machine units, the base alignment is 2N or 4N, respectively.
  3. If the member is a three-component vector with components consuming N basic machine units, the base alignment is 4N.
  4. If the member is an array of scalars or vectors, the base alignment and array stride are set to match the base alignment of a single array element, according to rules (1), (2), and (3), and rounded up to the base alignment of a vec4.
  5. If the member is a column-major matrix with C columns and R rows, the matrix is stored identically to an array of C column vectors with R components each, according to rule (4).
  6. If the member is an array of S column-major matrices with C columns and R rows, the matrix is stored identically to a row of S × C column vectors with R components each, according to rule (4).
  7. If the member is a row-major matrix with C columns and R rows, the matrix is stored identically to an array of R row vectors with C components each, according to rule (4).
  8. If the member is an array of S row-major matrices with C columns and R rows, the matrix is stored identically to a row of S × R row vectors with C components each, according to rule (4).
  9. If the member is a structure, the base alignment of the structure is N, where N is the largest base alignment value of any of its members, and rounded up to the base alignment of a vec4.
    The individual members of this substructure are then assigned offsets by applying this set of rules recursively, where the base offset of the first member of the sub-structure is equal to the aligned offset of the structure.
    The structure may have padding at the end; the base offset of the member following the sub-structure is rounded up to the next multiple of the base alignment of the structure.
  10. If the member is an array of S structures, the S elements of the array are laid out in order, according to rule (9).

訳してみたもの

  1. スカラーでNマシンユニットを消費する場合アラインメントはN。
  2. 2または4要素のベクトルで要素がNマシンユニットを消費する場合、アラインメントはそれぞれ2N、4N。
  3. 3要素ベクトルで要素がNマシンユニットを消費するときのアラインメントは4N。
  4. 配列のアラインメントは要素から計算される。ただしvec4で切り上げられる。
  5. 列優先C列R行行列はルール4からR要素ベクトルのサイズCの配列と同じ。
  6. 列優先C列R行行列のS要素配列の場合、R要素ベクトルを要素とするサイズS x Cの配列と同じ。
  7. 行優先C列R行行列の場合、C要素ベクトルのサイズRの配列と同じ。
  8. 行優先C列R行の行列S要素配列の場合、C要素ベクトルを要素とするサイズS x Rの配列と同じ。
  9. 構造体の場合、アラインメントは構造体のメンバーのうち一番大きいもののアラインメントになる。ただしvec4アラインメントに切り上げられる。
    構造体の個々のメンバーはこのルールを再帰的に適用され決まったオフセットの位置に配置される。構造体の最初のメンバーの配置オフセットは構造体自身のオフセットに一致する。
    構造体は末尾にパディングを持つ場合がある。構造体に続くメンバーのベースオフセットは、構造体のベースアライメントの次の倍数に切り上げられる。
  10. 構造体を要素とするサイズSの配列の場合、ルール9によって順番に配置される。

スカラー型は bool, float, int, uint, double があります。

スカラー型のアラインメントとバイトサイズは下のようになっています。

uniform type alignment byte size
bool 4 4
float 4 4
int 4 4
uint 4 4
double 8 8

それから行列のメモリレイアウトの列優先 (column-major)、行優先 (row-major) は glsl 側で指定できて column_major または row_major で指定します。 デフォルトは列優先です。

https://www.khronos.org/opengl/wiki/Interface_Block_(GLSL)#Matrix_storage_order

調べ方

仕様を読んだり google で検索したりしてたんですがはっきりした情報が見つからずコードを書いて実際にどうなるかみてみることにしました。

https://github.com/tkaaad97/uniformblock-experiment

使ったコードはここのリポジトリに置いています。

適当なユニフォームブロックが入ったシェーダーを書いてこれを OpenGLコンパイルし、ユニフォームの情報を取得して調べるという方法で試しました。

下のような感じでブロック一つ目のフィールドをアラインメントが一番小さい float にして二つ目のフィールドを調べたい型にし、三つ目を float にしてオフセットを取得することでアラインメントとサイズが多分わかると思います。

layout (std140) uniform ublock {
    float uniform1;
    #{uniformType} uniform2;
    float uniform3;
} ublock;

https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glGetActiveUniformsiv.xhtml

ユニフォームのパラメーターは glGetActiveUniformsiv で取得できます。

パラメーターの種類は GL_UNIFORM_SIZE, GL_UNIFORM_NAME_LENGTH, GL_UNIFORM_BLOCK_INDEX, GL_UNIFORM_OFFSET, GL_UNIFORM_ARRAY_STRIDE, GL_UNIFORM_MATRIX_STRIDE, GL_UNIFORM_IS_ROW_MAJOR GL_UNIFORM_ATOMIC_COUNTER_BUFFER_INDEX です。

また構造体についてはパターンが色々あって網羅できないためこの方法では調べていません。 他の部分が分かれば構造体については仕様のルールから理解できるはずと思います。

結果

下のような結果になりました。 仕様の文章からわかる結果だと思いますが、確証が持てなかったのではっきりしてよかったです。

uniform type alignment byte size array stride matrix stride matrix order
bvec2 8 8
bvec3 16 12
bvec4 16 16
vec2 8 8
vec3 16 12
vec4 16 16
ivec2 8 8
ivec3 16 12
ivec4 16 16
uvec2 8 8
uvec3 16 12
uvec4 16 16
dvec2 16 16
dvec3 32 24
dvec4 32 32
float[4] 16 64 16
vec2[4] 16 64 16
vec3[4] 16 64 16
vec4[4] 16 64 16
dvec2[4] 16 64 16
dvec3[4] 32 128 32
dvec4[4] 32 128 32
mat2 16 32 16 row
mat2x3 16 48 16 row
mat2x4 16 64 16 row
mat3 16 48 16 row
mat3x2 16 32 16 row
mat3x4 16 64 16 row
mat4 16 64 16 row
mat4x2 16 32 16 row
mat4x3 16 48 16 row
dmat2 16 32 16 row
dmat2x3 16 48 16 row
dmat2x4 16 64 16 row
dmat3 32 96 32 row
dmat3x2 32 64 32 row
dmat3x4 32 128 32 row
dmat4 32 128 32 row
dmat4x2 32 64 32 row
dmat4x3 32 96 32 row
mat2[4] 16 128 32 16 row
mat2x3[4] 16 192 48 16 row
mat2x4[4] 16 256 64 16 row
mat3[4] 16 192 48 16 row
mat3x2[4] 16 128 32 16 row
mat3x4[4] 16 256 64 16 row
mat4[4] 16 256 64 16 row
mat4x2[4] 16 128 32 16 row
mat4x3[4] 16 192 48 16 row
dmat2[4] 16 128 32 16 row
dmat2x3[4] 16 192 48 16 row
dmat2x4[4] 16 256 64 16 row
dmat3[4] 32 384 96 32 row
dmat3x2[4] 32 256 64 32 row
dmat3x4[4] 32 512 128 32 row
dmat4[4] 32 512 128 32 row
dmat4x2[4] 32 256 64 32 row
dmat4x3[4] 32 384 96 32 row
mat2 16 32 16 column
mat2x3 16 32 16 column
mat2x4 16 32 16 column
mat3 16 48 16 column
mat3x2 16 48 16 column
mat3x4 16 48 16 column
mat4 16 64 16 column
mat4x2 16 64 16 column
mat4x3 16 64 16 column
dmat2 16 32 16 column
dmat2x3 32 64 32 column
dmat2x4 32 64 32 column
dmat3 32 96 32 column
dmat3x2 16 48 16 column
dmat3x4 32 96 32 column
dmat4 32 128 32 column
dmat4x2 16 64 16 column
dmat4x3 32 128 32 column
mat2[4] 16 128 32 16 column
mat2x3[4] 16 128 32 16 column
mat2x4[4] 16 128 32 16 column
mat3[4] 16 192 48 16 column
mat3x2[4] 16 192 48 16 column
mat3x4[4] 16 192 48 16 column
mat4[4] 16 256 64 16 column
mat4x2[4] 16 256 64 16 column
mat4x3[4] 16 256 64 16 column
dmat2[4] 16 128 32 16 column
dmat2x3[4] 32 256 64 32 column
dmat2x4[4] 32 256 64 32 column
dmat3[4] 32 384 96 32 column
dmat3x2[4] 16 192 48 16 column
dmat3x4[4] 32 384 96 32 column
dmat4[4] 32 512 128 32 column
dmat4x2[4] 16 256 64 16 column
dmat4x3[4] 32 512 128 32 column

Jenkins の CodeBuild プラグイン の TIPS

はじめに

Jenkins から CodeBuild を使うためのプラグインAWS で提供されていて、このプラグインのちょっとした使い方についてです。

docs.aws.amazon.com

github.com

plugins.jenkins.io

検索してもこのプラグインの情報があまり無くて使われてないような感じがありますが Jenkins 使っている場合は便利な場合もあると思います。

使いそうなケースは以下のようなものがあると思います。

  • Jenkins で使える色々なトリガーで CodeBuild ジョブを実行したい
  • AWS マネジメントコンソールからやるより Jenkins のほうが慣れている人が多い
  • Jenkins に色々ジョブがあって、実行中に Jenkins サーバーに負荷をかけたくない

Switch Role して使いたい

CodeBuild プラグインを使うには CodeBuild 関連の IAM 権限が必要になります。

Jenkins サーバーを EC2 インスタンスで動かしている場合は EC2 インスタンスに付いているインスタンスプロファイルの IAM ロールが使えます。

しかし Jenkins では色々なジョブを動かしたりするので一つの IAM ロールにたくさん権限付けたく無いことがあります。

それから Jenkins が動いているのとは別の AWS アカウントの CodeBuild を実行したい場合も考えられます。

こういう時に Switch Role が便利です。

Switch Role についは下のドキュメントなどに書かれていました。

docs.aws.amazon.com

やり方

Jenkins の認証情報設定のところから CodeBuild Credentials を作成します。

f:id:tkaaad97:20191027011448p:plain

(画像は一部情報を書き換えています)

IAM Role ARN に Switch Role 先の IAM ロールを指定します。

インスタンスプロファイルの IAM ロールから Switch Role できるようになっていればアクセスキーなどは空のままで大丈夫でした。

CodeBuild プラグインを使うところでこの認証情報を credentialsId で指定します。

下はパイプラインジョブ中の書き方の例です。

node {
    stage('build') {
        awsCodeBuild(
            credentialsId: 'jenkins-codebuild-role-credentials',
            credentialsType: 'jenkins',
            projectName: 'hello',
            region: 'ap-northeast-1',
            sourceControlType: 'project'
        )
    }
}

AWS_PROFILE 環境変数で切り替えられるかもやってみたのですが、こちらは上手く行きませんでした。

環境変数を指定する

Jenkins ジョブをパラメーター化して実行時にパラメーターを渡し、これを CodeBuild ジョブの環境変数に渡したいときのやり方です。

なぜか独特なフォーマットになっているので注意が必要なことがあります。

フォーマットの説明は以下です。これは Jenkins 中のパイプラインシンタックスのページにあった情報です。

 Optional parameter. These environment variables will be passed to the StartBuild API and will override any project environment variables.
Example: [ { VAR, value } ]
Example: [ { VAR, value }, { VAR2, value } ]

Commas in the name or value must be escaped. For freestyle projects, escape with one backslash:
[ { VAR, value1\,value2 } ]
For pipeline projects, escape with two backslashes:
awsCodeBuild ... envVariables: '[{VAR, value1\\,value2}]'

カンマはエスケープが必要になっているようです。

また試していて環境変数の値に空文字列を渡すことができないようでした。 下のようなエラーが出ました。

[AWS CodeBuild Plugin] CodeBuild configured improperly in project settings
    > CodeBuild environment variable keys and values cannot be empty and the string must be of the form [{key, value}, {key2, value2}] (Service: null; Status Code: 0; Error Code: null; Request ID: null)

Jenkins ジョブにパラメーターを渡すところでこのプラグインの挙動を考慮するのは微妙なのでパイプラインジョブの中で書き換えて渡してやるのがいいような気がしています。 カンマのエスケープもパイプラインのコードの中で行ってもいいと思います。

node {
    stage('build') {
        def envVariables = null;
        if ("${PARAM1}" != '') {
            envVariables = "[{PARAM1,${PARAM1}}]"
        }

        awsCodeBuild(
            credentialsId: 'jenkins-codebuild-role-credentials',
            credentialsType: 'jenkins',
            projectName: 'hello',
            region: 'ap-northeast-1',
            sourceControlType: 'project',
            envVariables: envVariables
        )
    }
}

モーメントシャドウマップについて

はじめに

分散シャドウマップに続いてモーメントシャドウマップについてです。

モーメントシャドウマップの方が少し難しい感じでなかなか理解がすすまなかったんですが、論文のほかに補足的なドキュメントが出ていてそちらを見ていたら大体の流れが理解できてきた気がするのでまとめようと思います。

アルゴリズムの部分で出てきた式をなんとなく追いかける目的で書いているため色々と細かい部分は飛ばしています。

参考にしたもの

研究者の方のページに論文やスライド、発表動画、補足ドキュメントなど情報が色々載っていました。

momentsingraphics.de

cg.cs.uni-bonn.de

導出について詳しく書いてあった補足のドキュメントはこの pdf です。

http://cg.cs.uni-bonn.de/aigaion2root/attachments/MomentShadowMappingSupplementary.pdf

モーメントシャドウマップ

まずモーメントシャドウマップについて説明してみます。

分散シャドウマップと少し似ていて改良した方法になっています。

分散シャドウマップでは二次のモーメントまでを使いますが、モーメントシャドウマップではより高次のモーメント (通常は四次) まで利用します。

ライトブリーディングという遮蔽物の境界で本来影であるはずのところに光の筋ができてしまう問題が改善されているようです。

MIPMAP やガウシアンブラーなどのアンチエイリアシング手法も同様に使用できます。

アルゴリズム

論文の Algorithm 2 にニの倍数のモーメントでの計算方法が書かれています。

先に使われている変数をいくつか紹介します。

$z$ はライトからの深度です。

$\textbf{b}$ は深度からモーメントの列ベクトルを返す関数です。

$$ \textbf{b}(z) := (z, z^{2}, \ldots, z^{n})^{T} $$

$b$ は $\textbf{b}(z)$ の平均値です。

$\hat{\textbf b}$ は0乗からn-1乗までのモーメントの列ベクトルを返す関数です。

$$ \hat{\textbf b}(z) := (1, z, z^{2}, ..., z^{n-1})^{T} $$

$\hat{b}$ は $\hat{\textbf b}(z)$ の平均値です。

Algorithm 2

  1. $$n := \frac{m}{2} + 1$$

    モーメントの次数$m$から求める深度の数$n$が決まります。

  2. $$ B := (b _ {j+k-2}) _ {j,k=1} ^ n $$

    モーメント平均値から$B$行列を作ります。

  3. $$ z _ 1 := z _ f $$

    深度のうち一つは描画する部分の深度とします。

  4. $$ c := B^{-1} \cdot \hat{\textbf b}(z_{1})$$

    $B$逆行列とベクトル $\hat{\textbf b}(z_{1})$ の積を計算します。これを$c$とします。

  5. $$ \sum _ {k=1} ^ n c _ k z ^ {k-1} = 0 $$

    $c$と深度の $n-1$ 次の方程式を解いて $n-1$ 個の深度を求めます。

  6. $$\hat{A} := (z _ i ^ {j-1}) _ {j,i=1} ^ n $$

    求めた深度を使って $\hat{A}$ 行列を求めます。この行列はヴァンデルモンドマトリックスと呼ばれ逆行列を作れます。

  7. $$ w = \hat{A}^{-1} \hat{b} $$

    $\hat{A}$ の逆行列とモーメント平均値のベクトルから各深度の比率を求めます。

  8. $$ G := \sum_{i=1,z _ i \lt z _ f} ^ n w _ i $$

    描画部分の深度よりも小さい深度の比率を足して影の濃さを計算します。

導出

モーメントシャドウマップでは$n$個の深度に集中して分布するとして深度と比率を求めて解いています。

モーメント平均値は次のように表せます。$w _ i$ は深度 $z _ i$ の分布比率です。

$$ \hat{b} = \sum _ {i=1} ^ n w _ i \hat{\textbf b}(z _ i) $$

次に $\hat{\textbf b}(x _ {i})$ の直積の平均値を考えます。 これは$B$と等しくなります。

$$ \begin{aligned} \sum _ {i=1} ^ n w _ i \hat{\textbf b}(z _ i)\otimes\hat{\textbf b}(z _ i) &= (\sum _ {i=1} ^ n w _ i z _ i ^ {j-1} z _ i ^ {k-1}) _ {j,k=1} ^ n \\ &= (\sum _ {i=1} ^ n w _ i z _ i ^ {j+k-2}) _ {j,k=1} ^ n \\ &= (b _ {j+k-2}) _ {j,k=1} ^ n \\ &= B \end{aligned} $$

変形して次の形にできます。

$$ \begin{aligned} B &= (\sum _ {i=1} ^ n w _ i z _ i ^ {j-1} z _ i ^ {k-1}) _ {j,k=1} ^ {n} \\ &= (\sum _ {i=1} ^ {n}\sum _ {l=1} ^ {n} w _ {i} \delta _ {il} z _ {i} ^ {j-1} z _ {l} ^ {k-1}) _ {j,k=1} ^ {n} \\ &= \hat{A}\text{diag}(w)\hat{A} ^ {\textrm{T}} \\ &= \begin{pmatrix} 1 & 1 & \cdots & 1 \\ z _ {1} & z _ {2} & \cdots & z _ {n} \\ \vdots & \vdots & & \vdots \\ z _ {1} ^ {n-1} & z _ {2} ^ {n-1} & \cdots & z _ {n} ^ {n-1} \end{pmatrix} \begin{pmatrix} w _ {1} & & & 0 \\ & w _ {2} & & \\ & & \ddots & \\ 0 & & & w _ {n} \end{pmatrix} \begin{pmatrix} 1 & z _ {1} & \cdots & z _ {1} ^ {n-1} \\ 1 & z _ {2} & \cdots & z _ {1} ^ {n-1} \\ \vdots & \vdots & & \vdots \\ 1 & z _ {n} & \cdots & z _ {n} ^ {n-1} \end{pmatrix} \\ \hat{A} ^ {-1} B \hat{A} ^ {-\textrm{T}} &= \text{diag}(w) \end{aligned} $$

$\hat{A} ^ {T} B ^ {-1} \hat{A}$ も対角行列となります。

$$ \hat{\textbf b}(z _ {j}) ^ {\textrm{T}} B ^ {-1} \hat{\textbf b}(z _ {k}) = \begin{cases} 0 & \text{if} & j \ne k \\ 1 / w _ {j} & \text{if} & j = k \end{cases} $$

ここから Algorithm 2 のステップ4とステップ5の式が出てきます。

ステップ7の各深度の分布比率はモーメント平均値の式から出てきます。

$$ \begin{aligned} \hat{b} &= \sum _ {i=1} ^ {n} w _ {i} \hat{\bf{b}}(z _ {i}) \\ &= \hat{A} w \\ w &= \hat{A} ^ {-1} \hat{b} \end{aligned} $$

$m=2$ の場合

$m=2$ の場合は分散シャドウマップと同じ式になります。

(分散シャドウマップの論文に出ていたのは影にならない確率なので実際には1から引いた値と同じ)

$$ \begin{aligned} m &= 2 \\ n &= 2 \\ B &= \begin{pmatrix} 1 & b _ {1} \\ b _ {1} & b _ {2} \end{pmatrix} \\ A &= \begin{pmatrix} 1 & 1 \\ z _ {1} & z _ {2} \end{pmatrix} \\ z &= \frac{b _ {1} z _ {f} - b _ {2}}{z _ {f} - b _ {1}} \\ G &= \frac{(z _ f - \mu) ^ 2}{\sigma ^ 2 + (z _ f - \mu) ^ 2} \end{aligned} $$

$m=4$ の場合

Algorithm 3 の Hamburger 4MSM アルゴリズムというのが多分実際に実装に使われるアルゴリズムだと思います。

$B$逆行列が必要ですが計算誤差で正則でなくなってしまうことを避けるため$b$にバイアスを加えた $b'$ を使っているようです。

バイアスに使われている $\alpha$ は$0$以上の十分小さな値 (例えば $3.0 \cdot 10 ^ {-5}$ ) です。

$$ \begin{aligned} m &= 4 \\ n &= 3 \\ b' &= (1 - \alpha) b + \alpha \begin{pmatrix}0.5 \\ 0.5 \\ 0.5 \\ 0.5\end{pmatrix}\\ B &= \begin{pmatrix} 1 & b' _ 1 & b' _ 2 \\ b' _ 1 & b' _ 2 & b' _ 3 \\ b' _ 2 & b' _ 3 & b' _ 4 \end{pmatrix} \\ A &= \begin{pmatrix} 1 & 1 & 1 \\ z _ {1} & z _ {2} & z _ {3} \\ z _ {1} ^ 2 & z _ {2} ^ 2 & z _ {3} ^ 2 \end{pmatrix} \\ G &= \begin{cases} 0 & \text{if} & z _ f \leq z _ 2 \leq z _ 3 \\ \frac{z _ f z _ 3 - b' _ 1 (z _ f + z _ 3) + b' _ 2}{(z _ 3 - z _ 2)(z _ f - z _ 2)} & \text{if} & z _ 2 < z _ f \leq z _ 3 \\ 1 - \frac{z _ 2 z _ 3 - b' _ 1 (z _ 2 + z _ 3) + b' _ 2}{(z _ f - z _ 2)(z _ f - z _ 3)} & \text{if} & z _ 2 \leq z _ 3 < z _ f \\ \end{cases} \end{aligned} $$

$G$ は Wolfram alpha で同じ感じの答えが出るのが計算できました。

Simplify[Inverse[{{1,1,1},{z_f,z_2,z_3},{z_f^2,z_2^2,z_3^2}}][[2]].{1,b_1,b_2}]
Simplify[Inverse[{{1,1,1},{z_f,z_2,z_3},{z_f^2,z_2^2,z_3^2}}][[2;;3]].{1,b_1,b_2}.{1,1}]

思ったこと

最初は数か所の深度だけに分布すると仮定して計算するのは、それでいいのかなという疑問を持ちました。

ただ $m=2$ で分散シャドウマップで使われているチェビシェフの不等式と同じ結果となったのと、モーメントのみから正確な深度の分布を求めることはできないので下限の確率を求めるということがこの計算の意味なのかなと考えるとなんとなく納得できました。

ライトブリーディングが起こるような二箇所の深度に集中して分布する場合などにこの方法で対応できるということも理解できます。

ちょっと複雑だなという気はやっぱりしてしまいます。ライトブリーディングの問題が解決できれば他の方法でもいいかもとも思いました。

数式の表示

MathJax を使うようにしてみました。

同じ数式の書き方で VS Code などで Markdown 表示の確認ができて、はてなブログの Web 上で確認するよりも大分すばやく確認できて書きやすいと思います。

しかしはてなブログ特有の Markdown 記法と干渉したりして結局そのまま使えるわけではないので微妙のような気もします。

別のページに何か影響でてしまっているかとかもあまり確認してないので後で色々調整必要かもしれません。

分散シャドウマップについて

はじめに

分散シャドウマップ (Variance Shadow Maps) について調べたことのまとめです。

モーメントシャドウマップという手法の方が新しく提案されていて、これは分散シャドウマップを改良した方法のようです。

最初はモーメントシャドウマップを調べていたんですが、分散シャドウマップについて知らないと理解できなそうだったので分散シャドウマップを先に調べることにしました。

分散シャドウマップ

元の論文が Web 上で公開されていて読むことができました。

www.punkuser.net

http://www.punkuser.net/vsm/vsm_paper.pdf

最初は Web 上の解説記事などを読んだりしていたんですが、 この論文はそこまで難しくはなさそうだったので論文を読んだほうが確実な気がします。

分散シャドウマップについて説明してみます。

シャドウマップでの影の描画ではまずライトを視点として、ライトから一番近いポリゴンの深度をシャドウマップ用のテクスチャに書き出します。

それからカメラ視点からポリゴンを描画していき、描画するピクセル部分のライトからの距離がシャドウマップよりも遠い場合はライトとの間に遮蔽物があることがわかるので影が落ちるように明るさを変えて描画します。

シャドウマップの解像度が低い場合にエイリアシングが起こることがよくあります。

分散シャドウマップではエイリアシングが改善され、影の境界がスムーズに描画されます。

分散シャドウマップでは MIPMAP や異方性フィルタリングなど通常の画像に使われるエイリアシングの対策が有効に使えるというのも利点のようです。

描画の方法

  1. まずシャドウマップを作成します。ただし通常のシャドウマップと違ってライトからの深度と深度の二乗を出力します。

  2. 必要に応じて事前に2パスガウシアンブラーでフィルタリングを行います。

  3. シャドウマップの MIPMAP を生成します。OpenGL では MIPMAP を自動で生成する API が使えます。

  4. カメラ視点で描画を行います。フラグメントシェーダーでシャドウマップから深度と深度の二乗を取得します。シャドウマップから取得した値は局所的な平均値になっています。 描画部分のライトからの深度がシャドウマップの深度より小さい場合は影は落ちないと判定します。 描画部分のライトからの深度がシャドウマップの深度よりも大きい場合はチェビシェフの不等式を使って影の強さを計算して描画します。

チェビシェフの不等式について

分散シャドウマップで使われているのはチェビシェフの不等式の single tail バージョンです。

en.wikipedia.org

読み方がわかりませんがカンテリ (コンテリ?) の不等式と呼ばれているようです。

論文に出てくるのは下の式です。

{
P(x \geq t) \leq p_{max}(t) \equiv \frac {\sigma ^{2}}{\sigma ^{2}+(t - \mu)^{2}}
}

{t > \mu} という条件があります。

{\mu} は深度の平均値です。 {\sigma ^2} は深度の分散で {\mu} と深度の二乗の平均から求められます。

{
\sigma ^{2} = E(x ^2) - E(x) ^2
}

{P(x \geq t)} は影を作る遮蔽物の深度が t よりも大きい場合の確率です。 t は描画する部分の深度なのでこの確率は影にならない場合の確率だと思います。 t が大きくなると分母が大きくなり、影にならない確率は小さくなります。

vertex skinning の覚え書き

はじめに

3D モデルを読み込んで OpenGL で表示させようとしていてこのあたりを調べていたのでここに書いておきます。

glTF を主に調べていましたが、他のフォーマットなどでも同じ仕組みが使われてるようです。

glTF のドキュメントには vertex skinning という名前が出てましたが色々呼び方があって紛らわしいです。

ja.wikipedia.org

スケルタルアニメーションとかスキンメッシュアニメーション、ボーンアニメーションとも呼ばれるようです。

参考にしたもの

glTF のチュートリアルがわかりやすいと思います。

glTF-Tutorials/gltfTutorial_019_SimpleSkin.md at master · KhronosGroup/glTF-Tutorials · GitHub

glTF-Tutorials/gltfTutorial_020_Skins.md at master · KhronosGroup/glTF-Tutorials · GitHub

それから VSCode の glTF プラグインがあって、これでファイル内容を見つつプレビューしたりできて便利でした。

marketplace.visualstudio.com

定義へのジャンプができるため他の要素を ID で参照している部分などを追いかけるのがやりやすいと思います。

それからこちらは DirectX のフォーマットに関する記事ですが日本語で詳しいところまで解説されていて理解しやすかったです。

marupeke296.com

marupeke296.com

仕組みなど

vertex skinning は 3D モデルに色々なポーズを取らせたり、アニメーションさせて歩かせたり踊らせたりといったことに使えます。

やったことないですが Blender でモデルを作ってボーンをいれて動かすような場合も同じ仕組みが使われてると思います。

簡単に仕組みを説明してみます。

3D モデルはたくさんの三角形から成っていて、この三角形の頂点と紐付く情報を色々持っています。

頂点と紐付く情報は頂点の位置や色、テクスチャ座標などです。

vertex skinning ではモデルにボーンを埋め込んでボーンの動きに追随して各頂点が動くようになっています。

ボーンの数は三角形頂点の数に比べて少なく、三角形頂点の情報を更新することなくボーンの変形だけでモデルを動かすことができるので扱いやすくなっています。

ボーンというと棒が繋がったようなのを想像しますが実際は階層的な座標系の構造を表すようなものだと思います。

肩の座標系の下に肘の座標系があってさらにその下に手首、その下に指という感じです。

頂点の情報にはボーンのインデックスとウェイト (影響度) が追加されます。

シェーダーには頂点の情報のほかに各ボーンの変形を表す行列が渡されてボーンによる変形を反映した座標が計算されて描画されます。

glTF

glTF の構造のうち vertex skinning と関係したところを書いておきます。

ノード

ノードは階層的な構造を表すのに使われます。ボーンの構造もノードで表現されます。

仕様だとこのあたりです。

https://github.com/KhronosGroup/glTF/blob/1f78518ec26e805cd23580022ca49f4349b0fd65/specification/2.0/README.md#nodes-and-hierarchy

JSONnodes の型は配列でインデックスが各ノードの識別子として使われます。 仕様のバージョン1.0では nodes にオブジェクトを使ってキーを識別子として使っている例もあるんですがバージョン2.0では配列になっているようです。

ノードのプロパティは name, children, rotation, scale, translation, matrix, mesh, skin などがあります。

https://github.com/KhronosGroup/glTF/blob/1f78518ec26e805cd23580022ca49f4349b0fd65/specification/2.0/README.md#node

children に子ノードの識別子を指定して階層的な構造を表せます。

rotation, scale, translation, matrix はノードの変形です。

https://github.com/KhronosGroup/glTF/blob/1f78518ec26e805cd23580022ca49f4349b0fd65/specification/2.0/README.md#transformations

mesh はノードと紐付くメッシュの識別子です。skin はスキンの識別子です。スキンについては後で説明します。

vertex skinning の場合は一番上の階層のノードに meshskin が指定されるようです。

仕様が1.0から2.0に変わったときになくなっているプロパティがいくつかあるようです。

https://github.com/KhronosGroup/glTF/blob/1f78518ec26e805cd23580022ca49f4349b0fd65/specification/1.0/README.md#node

スキン

スキンもボーンの情報を持っています。

https://github.com/KhronosGroup/glTF/blob/1f78518ec26e805cd23580022ca49f4349b0fd65/specification/2.0/README.md#skins

https://github.com/KhronosGroup/glTF/blob/1f78518ec26e805cd23580022ca49f4349b0fd65/specification/2.0/README.md#skin

joints はボーンを構成するノードの識別子の配列です。ボーンのルートのノードは相対的に動かないので joints には入ってません。

inverseBindMatricesaccessor の識別子で joints の数と同じ要素数の 4x4 行列の配列を参照します。 inverseBindMatrices はボーンの変形を計算するときに必要になります。

その27 アニメーションの根っこ:スキンメッシュアニメーション(ボーン操作)

インバースバインドマトリックスは恐らくこちらの記事で書かれていたボーンオフセット行列と同じものだと思います。

例えば肩を回転させて二の腕を動かすときに、肩の座標を原点に一致させないとおかしな変形になってしまうためこの行列が必要になるのだと思います。

ボーンの変形行列の計算方法はチュートリアルに書かれていました。

https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_020_Skins.md#the-joint-matrices

jointMatrix(j) =
  globalTransformOfNodeThatTheMeshIsAttachedTo^-1 *
  globalTransformOfJointNode(j) *
  inverseBindMatrixForJoint(j);

まずインバースバインドマトリックス inverseBindMatrixForJoint(j) を掛けて頂点をボーンの座標系に移動させます。

それからボーンをグローバル座標系で変形させる行列を掛けます。

最後に globalTransformOfNodeThatTheMeshIsAttachedTo^-1 を掛けているのは後で使う modelViewMatrix にメッシュがアタッチされたノードの変形が含まれているからだと思います。

skeleton というプロパティが指定される場合があってこれはボーンのルートノードの指定に使われるようなんですが、ノードのプロパティで skin が指定される場合との違いがよくわかりません。

ライブラリによってはこの skeleton プロパティは無視されているようで、three.js の GLTFLoader では使ってないように見えます。

Go で Lambda 書いたときの覚え書き

はじめに

Go 言語はほぼ触ったことなかったんですがもうリリースから10年ぐらい経っているらしいです。

AWS Lambda を書くのに少し Go 言語を使ってまた使うかもしれないので覚えたことを書いておきます。

go1.12 のバージョンを使っていました。

まだ少ししか触っていないので色々間違いがあるかもしれません。

またここに書いていることは自分にとって少し分かりにくかったことなどの羅列的なものになります。

CodeCommit や GitLab で使う場合

Go では依存ライブラリを専用のサーバーではなく GitHub などから取得するようになっているようです。

GitHub の場合はあまり問題なく使えると思うんですが CodeCommit やセルフホストしている GitLab などの場合は少し独自の設定が必要なようでした。

これについて詳しく書いているところが見つからず数日はまっていた気がします。

github.com

この issue でなんとなく CodeCommit などでの使い方がわかりました。

デフォルトでは https で依存パッケージを取得するようなんですが、 メタデータを取得する API に対応している必要があって サーバーによってはこれに対応していないので https を使わないようにしてやる必要があるのだと思います。

~/.gitconfig の設定に以下を追加しました。

[url "ssh://git-codecommit.ap-northeast-1.amazonaws.com/"]
    insteadOf = https://git-codecommit.ap-northeast-1.amazonaws.com/

それからモジュール名末尾には .git をいれました。これはいらない場合もあるかもしれません。

go mod init git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/hello.git

パッケージ

下のようにソースの一行目にパッケージの指定があります。

package main

import (
    "fmt"
)

func main() {
    fmt.Println("hello, world")
}

最初は一つのファイルがパッケージと対応しているのかと思っていましたが、 これは間違いで Go のパッケージは一つのディレクトリと対応しているようです。

同じディレクトリにおいたファイルは同一のパッケージにする必要があって、 ファイルが分かれていても同じパッケージ内であれば import せずに関数などを利用できます。

それから同じリポジトリ内であっても import は相対的パスなどではなくホスト名を含むパッケージ名を使うのが普通のようでした。

Lambda を書いたりする場合に CLI ツールとしても使えるように ライブラリのパッケージ、Lambda パッケージ、CLI ツールパッケージと分けたりすることがあるんじゃないかと思います。

例えば下のようなディレクトリ構成になります。

hello/
├── app
│   └── main.go
├── lambda
│   └── main.go
└── lib
    └── hello.go

lib/hello.go はライブラリとして他のパッケージから使われる関数などを含んでいます。 パッケージで公開される関数や定数の名前は大文字で開始するという決まりになっているようです。 これは人によって好み分かれそうですが export を別に書かなくていいのは楽だなと思いました。

package hello

import (
    "fmt"
)

func Hello() {
    fmt.Println("hello, world")
}

app/main.goCLI ツールなどのパッケージで lib/hello.go を import して使います。 import する名前にはソースリポジトリ名にパッケージのディレクトリのパスを追加して指定します。 ライブラリではなくアプリケーションとして実行するパッケージは main パッケージにして main 関数を定義します。

package main

import (
    "git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/hello.git/lib"
)

func main() {
    hello.Hello()
}

go build コマンドで実行ファイルをビルドします。

go build -o hello ./app

go run コマンドでそのまま実行もできます。

go run ./app

スライス

スライスはランダムアクセスできて要素の追加もできるコンテナです。 C++ の std::vector に近いものだと思います。 内部に要素サイズと、確保したサイズ、ポインタを保持するような実装になってるんじゃないかと思います。 std::vector のように要素をメモリ上に連続で確保するようになっていて、足りない場合は新しくメモリを確保して作り直すようです。 下のようなコードでポインタの値が変わっているのが見られました。

package main

import (
    "fmt"
)

func main() {
    a := make([]int, 2,  3)
    fmt.Println(a) // [0 0]

    a[0] = 1
    a[1] = 2
    fmt.Println(a) // [1 2]

    fmt.Printf("%v %p %p\n", a, &a, &a[0]) // [1 2] 0xc00000c060 0xc000014380

    a = append(a, 3)
    fmt.Printf("%v %p %p\n", a, &a, &a[0]) // [1 2 3] 0xc00000c060 0xc000014380

    a = append(a, 4)
    fmt.Printf("%v %p %p\n", a, &a, &a[0]) // [1 2 3 4] 0xc00000c060 0xc0000181b0
}

素数がわかる場合は指定したほうが余分なメモリ確保やコピーが減るので速くなると思います。 これは std::vector で reserve 使ったりするのと同じ感じだと思います。

マップ

マップはキーと値を持つコンテナです。 大体は C++ の std::map と似ているんですがキーはソートされていない構造のようです。 map の要素をイテレートするとき順序が保証されないことに気を付ける必要があります。

下のコードを実行すると順序がバラバラになっているのが見られました。

順序固定する場合はキーを一度取得してソートしてからキーでアクセスします。

package main

import (
    "fmt"
)

func main() {
    a := map[string]int{
        "a": 1,
        "b": 2,
        "c": 3,
        "d": 4,
        "e": 5,
    }

    fmt.Println(a)

    for i := 0; i < 10; i++ {
        for k, v := range a {
            fmt.Printf("%s %d\n", k, v)
        }
    }
}

JSON

golang.org

標準で入っている JSON のライブラリが使えます。 json.MarshalJSON への変換、 json.UnmarshalJSON から struct などの変換ができます。

struct はそのまま何もしなくても JSON にできます。

フィールドにタグを付けて少しカスタマイズすることもできます。

package main

import (
    "encoding/json"
    "fmt"
)

type Person struct {
    Name string `json:"name"`
    Age int `json:"int"`
    PhoneNumber string `json:"phone_number,omitempty"`
}

func main() {
    a := Person{
        Name: "Taro",
        Age: 3,
    }

    bytes, err := json.Marshal(a)
    if err != nil {
        panic(err)
    }

    fmt.Println(string(bytes))

    b := Person{}
    err = json.Unmarshal(bytes, &b)
    if err != nil {
        panic(err)
    }

    fmt.Println(b)
}

独自に変換処理を書きたい場合は MarshalJSON と UnmarshalJSON を定義してやればいいようです。

json - The Go Programming Language

Example (CustomMarshalJSON) の部分に例がありました。

テスト

go test のコマンドで簡単にテスト実行できるようになっています。 hoge_test.go のようなファイル名にして Test ではじまる名前の関数を定義するとテストとして実行されます。

godoc.org

テストで assert を書くのにこのライブラリが便利でした。

package main

import (
    "github.com/stretchr/testify/assert"
    "testing"
)

func TestA(t *testing.T) {
    a := []string{ "aaa", "bbb" }
    b := []string{ "aaa", "BBB", "ccc" }
    assert.Equal(t, a, b)
}

CLI

godoc.org

CLI ツールを書くときに使えるライブラリも色々あるようなんですが cobra というライブラリを少し使いました。

少し使った感じはサブコマンドも書けて、引数やオプションのキャプチャも簡単だったのでよさそうに感じました。

aws-cli を使ったシェルスクリプトなどを書いていてオプションとか扱うのがなかなか大変なので Go で書いて aws-sdk-go 使うのもいいかもなと思いました。

Lambda や CodeBuild でも多分使えると思うので実行もしやすいと思います。

AWS SAM

aws.amazon.com

docs.aws.amazon.com

Go 言語関係無い話ですが Lambda を書くときに AWS SAM を使うと便利でした。

CloudFormation に変換されるテンプレートで Lambda や CloudWatch イベント、API Gateway、IAM ロール などのリソースを定義して AWS SAM のコマンドでパッケージ化とデプロイが簡単にできるようになっています。

ローカル環境で Docker を使って Lambda の挙動をテストすることもできます。

AWS SDK

Go 言語向けに AWS SDK のライブラリが公開されていてこれを使って AWSAPI を色々利用できます。

aws.amazon.com

aws-sdk-go-v2 というライブラリもあるようなんですがまだ Developer Preview の段階でした。

github.com

基本的に API 呼び出すためのライブラリですが一部ユーティリティの機能も追加されていてページネーションに対応した便利な関数などもあるようでした。

godoc.org

テスト用のモックも用意されているようです。

godoc.org

感想

使う前は機能が色々足りてないような印象だったんですが書いてみると意外と書きやすく感じました。

書きにくいと感じたり他の言語でできることができないために自分で書かなければいけないようなコードが増えたりもするんですが、 こういう言語だから仕方ないとあきらめて書いているとあまり悩まずに書ける気がしました。

コンパイラで静的に色々チェックされるのも安心感があって Python とか シェルスクリプトとかで色々書くのよりも自分にはあってる気がしました。