simplestarの技術ブログ

目的を書いて、思想と試行、結果と考察、そして具体的な手段を記録します。

Imagemagick:画像のエッジを引き延ばして格子状に再配置

前書き

キューブの見た目を更新する目的で動いています。

前回は Shader ができたところまで
simplestar-tech.hatenablog.com

今回はテクスチャを連番で結合する処理を作ります。
大分前に python の pillow で画像処理を書きましたが…
github.com
処理が遅すぎるのと
画像のエッジの引き延ばしを行っていなかったので、キューブ群を遠目で見ると結合部分が光ったり黒くなったり…とにかく見た目が良くない
その部分を今回は imagemagick を利用して解決していきます。

参考までに、昔のキューブの見た目は頑張ってこんなの
simplestar-tech.hatenablog.com

もう2年以上、技術選定を続けているという…今年は完成させるぞ!
simplestar-tech.hatenablog.com

Imagemagick の基本を習得

まずは Windows 環境で使えるように portable 版をダウンロードして展開し PATH を通しておきます。
imagemagick.org

以降、Windows 環境だと convert コマンドを magick にしなければバッティングする旨を imagemagick が教えてくれるので
magick コマンドを利用します。
そうしたことを念頭に次のマニュアルを完走して、imagemagick を身体になじませました。

imagemagick.biz

Imagemagick での画像のエッジ引き延ばし

情報ソースはこちら
www.imagemagick.org

細かいことは置いとくと、次の倍率を指定するだけで余白を引き延ばすことができます。
(残念ながら distort は基本操作には出てこない)

magick in.png -set option:distort:viewport %[fx:w*(1+0.05)]x%[fx:h*(1+0.05)] -virtual-pixel Edge -distort SRT "0,0 1,1 0 %[fx:w/(2/0.05)],%[fx:h/(2/0.05)]" out.png

ちょっとだけ解説すると
distort:vieport が left, top 引き延ばし幅に関与&全体の幅高さ倍率指定
virtual^pixel Edge が right, bottom の引き延ばし幅に関与していて、要するに 1 引いた倍率を 1/2 倍で指定というながれ

画像を変換してタイル状に並べる

変換した画像を利用するという小技について、まずはおさらい。
こちらの画像を入力に(昔、自分が描いた絵です)

f:id:simplestar_tech:20200210204717j:plain
in.jpg

こちらの処理を実行すると

magick in.jpg -resize 200x200 -size 300x300 xc:blue +swap -gravity center -compose over -composite -mattecolor red -frame 10x10 exit.jpg

意味は、200x200 にアスペクト比を保ったまま縮小して、blue 一色で塗りつぶした 300x300 の画像を新規作成、並び順を入れ替えて、conposite により青を背景に重ね合わせて、10pixel 幅の赤いフレームを追加
という文(読めました?)

次の結果が得られます。

f:id:simplestar_tech:20200210204915j:plain
exit.jpg

PowerShell で実行する複数ファイル作成とタイリングモンタージュ

以下の powershellWindows 環境で imagemagick に PATH が通っていれば動きます。

テクスチャはこちらの有料アセット(single entity license)を購入したものを利用します。
Yughues PBR Nature Materials
assetstore.unity.com

tga ファイルは magick で変換すると上下逆転しますし、Specular は png に tga から変換すると背景色が真っ白に変わるので(本当は黒)
一度 jpg に中間ファイルを出して、上下反転しつつもなんとか
あと、ファイルによっては正方形ではないので正方形に直します。
加えて、左右に 8 pixel の余白を設けるように 1008 x 1008 に圧縮してタイリングしたいと思います。

を実現する Powershell が以下の通り

# [AO, Diffuse, Height, Normal, Specular]
$target = "Specular"
New-Item $target -ItemType Directory -Force
$dir = "~\Assets\Yughues PBS Nature Materials"
$imgs = Get-ChildItem -Recurse -Path "$dir\*$target.tga"
foreach ($img in $imgs) {
    $fullName = $img.FullName
    $jpg = "$target\" + [io.path]::ChangeExtension($img.Name, "jpg")
    $id = identify $fullName
    $edge = 0.0158730
    if ($id -like "*512x1024*") {
        magick $fullName -resize 504x1008 -flip -write mpr:i +delete mpr:i mpr:i +append -set option:distort:viewport "%[fx:w*(1+$edge)]x%[fx:h*(1+$edge)]" -virtual-pixel Edge -distort SRT "0,0 1,1 0 %[fx:w/(2/$edge)],%[fx:h/(2/$edge)]" -quality 100 $jpg
    }
    else {
        magick $fullName -resize 1008x1008 -flip -set option:distort:viewport "%[fx:w*(1+$edge)]x%[fx:h*(1+$edge)]" -virtual-pixel Edge -distort SRT "0,0 1,1 0 %[fx:w/(2/$edge)],%[fx:h/(2/$edge)]" -quality 100 $jpg
    }
    echo $jpg
}
montage "$target\*$target.jpg" -tile 8x8 -geometry 1024x1024 "0_$target.png"

出力されるテクスチャを使って、Height map を利用しつつ絵を作ることができました。

f:id:simplestar_tech:20200211123626p:plain
8x8 の 64 分割テクスチャによる 1 マテリアル描画で全キューブ描画

ステップ2はクリアです。
残すはゲーム内の UV 操作のみ

追記

余白をエッジ引き延ばしするのもよかった
だけど parallax で使う場合はかなり余白を利用することになるし、そこがエッジ引き延ばしだとまた不自然な見た目になったので
リピートラッピングする方法を考案しました。

次の命令は画像の上下左右ななめ方向すべてにタイリングする例です

magick in.jpg -resize 960x960 -flip -write mpr:i +delete mpr:i mpr:i mpr:i +append -write mpr:j +delete mpr:j mpr:j mpr:j -append -gravity center -crop 1024x1024+0+0 -quality 100 out.jpg

これをスクリプトにつなげると

# [AO, Diffuse, Height, Normal, Specular]
$target = "Diffuse"
New-Item $target -ItemType Directory -Force
$dir = "D:\github\Unity\UniversalParallaxOffset\Assets\Yughues PBS Nature Materials"
$imgs = Get-ChildItem -Recurse -Path "$dir\*$target.tga"
foreach ($img in $imgs) {
    $fullName = $img.FullName
    $jpg = "$target\" + [io.path]::ChangeExtension($img.Name, "jpg")
    $id = identify $fullName
    if ($id -like "*512x1024*") {
        magick $fullName -resize 480x960 -flip -write mpr:i +delete mpr:i mpr:i +append -write mpr:j +delete mpr:j mpr:j mpr:j +append -write mpr:k +delete mpr:k mpr:k mpr:k -append -gravity center -crop 1024x1024+0+0 -quality 100 $jpg
    }
    else {
        magick $fullName -resize 960x960 -flip -write mpr:j +delete mpr:j mpr:j mpr:j +append -write mpr:k +delete mpr:k mpr:k mpr:k -append -gravity center -crop 1024x1024+0+0 -quality 100 $jpg
    }
    echo $jpg
}
montage "$target\*$target.jpg" -tile 8x8 -geometry 1024x1024 "0_$target.png"

期待通り

Unity:URPでParallaxMapping

# 前書き

私が作っているキューブの世界
simplestar-tech.hatenablog.com

そろそろキューブの見た目を更新する時が来た

この先のステップとしては

1.AO, Albedo, Height, Normal, Specular テクスチャを入力とする、Height Map をちゃんと利用する Shader Graph が作られている
2.8192 x 8192 のテクスチャにインデックスで区分けして AO, Albedo, Height, Normal, Specular テクスチャ計5枚を用意、余白を指定して高速出力できるプログラムが作られている
3.余白を考慮したテクスチャ座標サンプルができるゲームロジックになっている

が想像できる

今回は ParallaxOffset を使った1.ステップについて解説

# Shader Graph

ここのところだいぶ Shader Graph に慣れてきたので、UV 操作する ParallaxOffset SubGraph があるならこんな感じのグラフになる

f:id:simplestar_tech:20200210122756p:plain
SimpleParallaxOffset

肝心の SubShader の中身がこれ

f:id:simplestar_tech:20200210122915p:plain
Simple Parallax Offset

見た目はこんな感じで

f:id:simplestar_tech:20200210123016j:plain
OFF, ON, OFF

情報源はこちらのやりとり

How do I use a Heightmap in Shader Graph?
https://forum.unity.com/threads/how-do-i-use-a-heightmap-in-shader-graph.538170/

後に解説を求めて参考にしたフォローアップ記事
LearnOpenGL - Parallax Mapping

まとめ

即興で作れるグラフなので
ソース公開しなくてもいいかな

Unity: URPのモザイクシェーダーと水面シェーダー

f:id:simplestar_tech:20200209162015j:plain
Simple Interactive Water

1/14 に twitter で知り合った方に、その方が作成したオンライン VR ゲームに誘われてログイン
こちらのマイクの設定が初めてで最後までつなぎ方がわからなかったのだけど、面白い話を聞かせてもらいました。

なんとも VR 空間の、それも Unity の URP でモザイクとインタラクティブな水面シェーダーがあるとうれしいとのこと

モザイクシェーダーはその日話を終えた 22:00 から作り始めて、朝2:00頃までにいきおいで完成させて公開
github.com

その後も、URP VR 空間における水の表現は自分もほしいところだったので、作り始めます。
Twitter に水面シェーダーの制作過程を記したところ、最大でツィートインプレッションが 15万を超えて
この20日でフォロワーが 500人くらい増えるなど、知名度が上がる出来事がありました。

後にこの出来事が人生を大成功へ導く大きな一歩だったと気づくことになるのですが、それはまた別のお話(おい

とりあえず、一連のシェアされたツィートを並べると次の通り


何が言いたいかというと

まとめ

ここのところ hatena の記事数は少ないけど、アクティブに学習とアウトプットを続けて、ちゃんとキューブのゲームにつながる技術習得とアセット入手まで進んでますよ自分
という、いつか記事数だけ見て、疑問に思っている自分にメッセージを残しておきます。

そうそう、これも記録してなかったけど PlayFab Meetup #2 で発表もしてきました。
なかなか好評でしたよ。
jpfug.connpass.com

自分の発表資料はこちらで公開中
docs.google.com

Unity:QuadのSubDivision Script 実装例

前置き

f:id:simplestar_tech:20200126194256p:plain
メッシュが細かければ、height 指定でこのような見た目が作れます

URP で Tessellation がいまだ用意されていない(調べた限り HDRP は可能)

Tesselation というのはこういうメッシュ細分化
ja.wikipedia.org

いますぐ平面のメッシュ細分化処理がほしい!

Mesh 操作の基本を確認

qiita.com

あ、作れる!

作った

        var subDivIndices = new int[6 * subDivCount * subDivCount];
        var subDivVerts = new Vector3[4 * subDivCount * subDivCount];
        var subDivUvs = new Vector2[4 * subDivCount * subDivCount];
        var edgeLength = 1.0f / subDivCount;
        for (int xIndex = 0; xIndex < subDivCount; xIndex++)
        {
            var offsetX = edgeLength * xIndex;
            for (int yIndex = 0; yIndex < subDivCount; yIndex++)
            {
                var offsetY = edgeLength * yIndex;
                var offsetIndex = subDivCount * xIndex + yIndex;

                var leftBottom = new Vector3(offsetX - 0.5f, offsetY - 0.5f);
                var rightBottom = leftBottom + new Vector3(edgeLength, 0);
                var leftUp = leftBottom + new Vector3(0, edgeLength);
                var rightUp = leftBottom + new Vector3(edgeLength, edgeLength);

                subDivVerts[4 * offsetIndex + 0] = leftBottom;
                subDivVerts[4 * offsetIndex + 1] = rightBottom;
                subDivVerts[4 * offsetIndex + 2] = leftUp;
                subDivVerts[4 * offsetIndex + 3] = rightUp;

                var uvLeftBottom = new Vector2(offsetX, offsetY);
                var uvRightBottom = uvLeftBottom + new Vector2(edgeLength, 0);
                var uvLeftUp = uvLeftBottom + new Vector2(0, edgeLength);
                var uvRightUp = uvLeftBottom + new Vector2(edgeLength, edgeLength);

                subDivUvs[4 * offsetIndex + 0] = uvLeftBottom;
                subDivUvs[4 * offsetIndex + 1] = uvRightBottom;
                subDivUvs[4 * offsetIndex + 2] = uvLeftUp;
                subDivUvs[4 * offsetIndex + 3] = uvRightUp;

                subDivIndices[6 * offsetIndex + 0] = 4 * offsetIndex + 0;
                subDivIndices[6 * offsetIndex + 1] = 4 * offsetIndex + 3;
                subDivIndices[6 * offsetIndex + 2] = 4 * offsetIndex + 1;
                subDivIndices[6 * offsetIndex + 3] = 4 * offsetIndex + 3;
                subDivIndices[6 * offsetIndex + 4] = 4 * offsetIndex + 0;
                subDivIndices[6 * offsetIndex + 5] = 4 * offsetIndex + 2;
            }
        }

        var subDivMesh = new Mesh();
        subDivMesh.name = "subDivMesh";
        subDivMesh.SetVertices(subDivVerts);
        subDivMesh.SetTriangles(subDivIndices, 0);
        subDivMesh.SetUVs(0, subDivUvs);
        subDivMesh.RecalculateBounds();
        subDivMesh.RecalculateNormals();
        subDivMesh.RecalculateTangents();

結果

Unity:はじめてのAddressable Assets(AWS CloudFront に S3 バケット設定でCDNを有効化)

前書き

未公開の作業記事が形にならないまま積み重なっていますが、明確なゴールとして切れる課題が見つかったので今回の記事を書きます。

はじめての Addressable Assets と題して、AWS の CloudFront についてもはじめて触ってみて
Unity のアセットを CDN 経由でダウンロードして利用するところまで確認してみます。

季節に応じてタイトルの BGM を変えたり、キューブのマテリアルをリッチなものにブラッシュアップしたりと
ゲームをリリースした後もロジック以外の装飾部分を更新したくなることもあるかと思い、あらかじめ AssetBundle を利用する仕組みを入れて初回リリースしたいと考えています。

それを支えるコンテンツ配信方法の今を確認して Unity ゲームに取り入れてみることにしました。

AWS CloudFront のファイルダウンロードの確認

Wep ページのように何万人から閲覧されても大丈夫な仕組みが CDN です。(コンテンツ配信ネットワークのこと)
Amazon のサービスに CDN なら CloudFront を使えという教えがあります。(ただの宣伝)

用語として CloudFront 一つの単位をディストリビューション(配信)と呼ぶそうで、サービスの最初のアクションは配信の作成になります。
配信にはオリジナルとなるファイルが必要で、このオリジナル指定には Amazon S3 のシンプルストレージサービスを利用するのが定石のようす。

具体的な手順は S3 のバケットを作り
CloudFront の配信を作るときに S3 のバケットを指定し、Origin Access Identity の自動生成により世界から秘匿されている S3 にアクセスできる唯一の存在として配信を作ります。

詳細な手順はここに書いてあります。
dev.classmethod.jp

S3 のダウンロード url を指定すると CloudFront を経由して、手元にダウンロードができるようになりました。
これにて一件落着(CloudFront ってこんなに扱いが楽だったのか)

Addressable Assets の利用

基本は Haruma:K さんのこちらの記事シリーズを読むだけ
light11.hatenadiary.com

AssetBundle の対象にしたい Prefab や AudioClip にアドレスを振って、ディフォルトの Group に所属させます。
気になる AssetBundle のビルドを行うと次のグループの設定に示されている Build パスに .bundle ファイルが出力されます。

f:id:simplestar_tech:20200113213020p:plain
グループの設定

さらに
light11.hatenadiary.com
LocalLoadPathとRemoteLoadPathにはhttp://[PrivateIpAddress]:[HostingServicePort]と入力しておけば Hosting Service によりローカルでダウンロード込みの動作確認ができました。
(BuildPath の方も Local と Remote それぞれ LoadPath に合わせてビルドしておく必要がある様子、プロファイルを切り替えるだけではロード元が切り替わらなかったような…理解を妨げる現象だった)

本当の更新タイミングがまだ自信ないけど、プロファイルを作って切り替えるだけで、様々なビルド設定を試すことができたので、概要をつかむことができました。
AssetBundle ビルドした成果物を CloudFront からダウンロードできるように RemoteLoadPath のサーバー(S3)に配置するだけで、表題のはじめての Addressable Assets が確認できました。
CloudFront 経由で AssetBundle をダウンロードして、ゲーム内で AssetReferenceとして使うことができました。

キャッシュの保存先

再読み込みとかの試験で困ったので、記録だけしますね。
Windows 環境だと
C:\Users\yourname\AppData\LocalLow\Unity の下に DefaultCampanyName_ProductName というフォルダが作られて、そこに保存されます。
試しに RemotePath に不正な値を入れても、キャッシュがあるかぎり成功しました。
逆に、キャッシュをクリアするとだめになり、ちゃんと CloudFront から AssetBundle を CDN で引けていることを確認できました。

暗号化が必要?

製品に利用するなら暗号化も考えておかないと、ユーザーがテクスチャとか、次のリリース準備物を先取りしてしまうので、必要なのかなーと考えましたが
調べてみると、なんとも、Unity の Addressable Assets の恩恵が受けられなくなるとかで
暗号化はサポートされるまでは Addressable Assets を利用した暗号化なしのゲームにしておこうと思います。

まとめ

基本的に外部記事を調べて、はじめて触る場合でも困らない情報が示されていたこと
試すことで、以前から気になっていた CDN で AssetBundle を配信する手順が確認できてよかった。

これからAddressable のグループをどう定義するか考えていけばいいのかな?

「NORMALMAP ONLINE」ノーマルマップをノイズ画像から作る

前置き

水面の波を作ろうと思って、Normal Map 作成の壁?に当たったので、濃淡画像に sobel フィルターとか与えて法線マップ作るプログラムを書こうとしたのですけど
一般的な問題すぎるので、ネットに転がっているだろうと調べたら、Web アプリとして公開されていました。

NORMALMAP ONLINE

次の URL を開きます。
https://cpetry.github.io/NormalMap-Online/

次の画面になるので、左下の濃淡画像をクリックしてファイル選択 Download ボタンを押すと Normal Map が手に入りました。

f:id:simplestar_tech:20200105152742p:plain
作成画面

お金払おうとしたけど
日本円の PayPal だと寄付できないんだって…残念

こうやって記事を書いて貢献しますね

2019年を振り返る

1月

VRMとやらを触ったり、新しい InputSystem とかゲームの土台に導入してみたり、Unity でのゲーム作りについて勉強してました。

simplestar-tech.hatenablog.com

3月

AWS を Unity から直接さわるなど、オンラインゲームを作るためのインフラの調査とか始めてました。

simplestar-tech.hatenablog.com

4月

不足した情報をもとにオンラインゲームを構想してました。やりたいことを書いては、できないことを確認していた頃

simplestar-tech.hatenablog.com

5月

オンライン要素には二系統あって、ひとつは全員で一つの世界データの共有、もう一つは少数のプレイヤー間のアニメ同期です。
GW期間では Cy# さんの MagicOnion を使って少数プレイヤー間のアニメーション同期部分を試していました。

simplestar-tech.hatenablog.com

6月

ECS でメッシュ生成の土台作りに入ります。

simplestar-tech.hatenablog.com

7月

LWRP で VRM のシェーダーを生かしたかったのでシェーダーを作るなど

simplestar-tech.hatenablog.com

8月

全員で一つの世界データの共有するため、バックエンドとして PlayFab を調べ始めたのがこの頃

simplestar-tech.hatenablog.com

9月

少数プレイヤー間でアニメ同期する部分と世界データ共有の部分をつなぎ合わせ
マウスでクリックするとキューブが破壊されるなど、クライアントサイドだけで基本を揃え始めます

simplestar-tech.hatenablog.com

10月

まだ世界データをどういったデータベースに配置しようか悩んでますね。Azure に手を出してみたり

simplestar-tech.hatenablog.com

11月

ゲームに特化したバイナリキャッシュサーバーを作って一つの世界データを共有する話を解決します。

simplestar-tech.hatenablog.com


不正できないキューブ操作を考え始めました。

simplestar-tech.hatenablog.com

12月

構想した内容をだいたい形にしたところ

simplestar-tech.hatenablog.com

まとめ

オンライン要素を頑張って習得した一年だったのかな。