3D モデルからドット絵生成

はじめに

ゲームの素材に使うようなドット絵のアニメーションを作ったりしたくて 3D モデルから生成するのを試してみました。

ドット絵の素材も売られてたりしますがなかなか自分の使いたいサイズやモーションの条件に合うものが見つかりませんでした。

3D モデルは Unity のアセットストアや Unreal Engineマーケットプレイスでたくさん販売されています。 また一部モーションがないようなときモーションだけ別のところから持ってきてリターゲットして使うということもできます。 Blender では深度も出力できるのでラインティングの効果に使ったりできるかもしれません。

前に Unreal Engineマーケットプレイスで無料でもらった 3D モデルがあったので これを使って Blender でドット絵生成するというのをやってみました。

UE4 からエクスポートして Blender にインポート

試したバージョンは以下です。

公式ドキュメントなどを見てもらった方が確実だと思うのでひっかかった点だけ書いておきます。

UE4 側で配置するときにトランスフォームの位置や回転を0にしておいた方がいいです。 Blender に読み込んだときにアニメーションの回転位置がずれてモデルが崩れてしまうことがありました。

それからメッシュに親子関係がついていて親のアニメーションに合わせて子も動くようなモデルは上手くとりこめませんでした。 この場合は Blender 側で親子関係を修正すると直せることがありました。 子のメッシュのオブジェクトプロパティから関係の設定を修正し、ペアレントを子のアーマチュアに設定します。 子のエンプティのペアレントを親の適切なボーンに設定します。

今回使わせてもらったモデルはこちらです。現在は無料ではなくなってます。

https://www.unrealengine.com/marketplace/ja/product/modular-rpg-heroes-polyart

このモデルはシンプルで色も少ないので向いてそうです。 複雑なモデルだと上手くいかないかもしれません。

ドット絵っぽくする方法

  • 解像度を下げる
  • 輪郭線を付ける
  • 色数を制限する

この三つを考えてやってみました。 Blender のシェーダーやコンポジットでかなり複雑なこともできるので他の効果も色々作れると思います。

シェーダー

シェーダーでは色数を制限する部分をやっています。 最初は全部コンポジットでやっていたんですがシェーダーでやる方がおそらく GPU が使えて速くなると思って作り直しました。

シェーダーは下のようになっています。 Blender 使いはじめたばかりなので使い方がおかしいところがあるかもしれません。

f:id:tkaaad97:20200723112156p:plain
シェーダー内容

ベースカラーが三段階の明るさでレンダリングされるようにしています。 今回使ったモデルは元々色数が少なく10色ぐらいが三段階で30色ぐらいになっています。

ベースカラーの色数が多いモデルの場合はテクスチャ画像を加工してパレット化して色数減らすなどできると思います。

コンポジット

コンポジットで解像度を下げるのと輪郭線を付けるのをやっています。

f:id:tkaaad97:20200723112348p:plain
コンポジットの内容

輪郭線用にシーンを分けてレンダリングしています。

輪郭を付けるのは freestyle の機能で簡単にできました。 シーンを分けているのは分けた方が輪郭がはっきり出やすいかなと思ったんですが実際はそんなに大きな違いはないかもしれません。

輪郭線の太さは解像度に合わせて調整する必要があります。

解像度を下げるのはピクセル化ノードでできます。

f:id:tkaaad97:20200723112427p:plain
ピクセル

https://docs.blender.org/manual/en/latest/compositing/types/filter/pixelate.html

ピクセル化ノードではアンチエイリアスは行われないので色数は変わりません。 ピクセル化ノードを使わず出力プロパティで低解像度にして、サンプリング数を1にするというのでも解像度下げることはできます。 ピクセル化ノード使う方が設定箇所が少ないのでいいかなと思って使っています。 ただ出力画像サイズは大きいままなので別にリサイズしてやる必要があります。

生成したドット絵

f:id:tkaaad97:20200723112927g:plain
ドット絵1

f:id:tkaaad97:20200723115203g:plain
ドット絵2

まあまあ上手くできている気がします。 完璧ではないですがこれを元に調整したりすれば素材として使えるかなと思います。

参考

www.youtube.com

dskjal.com

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 では使ってないように見えます。